Merge branch 'lyswhut:dev' into dev
This commit is contained in:
commit
19549209d0
35
CHANGELOG.md
35
CHANGELOG.md
@ -6,6 +6,41 @@ 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.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),如果你有服务器,可以将其部署到服务器上作为私人多端同步服务使用,详情看该项目说明
|
||||
|
||||
### 不兼容性变更说明
|
||||
|
||||
- 同步功能,从这个版本起,数据同步功能至少需要移动端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源某些歌单因为歌曲信息缺失导致打开失败的问题
|
||||
- 修复连续选择时的初始选择歌曲位置被意外改变的问题
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新 Electron 到v22.3.4
|
||||
|
||||
## [2.1.2](https://github.com/lyswhut/lx-music-desktop/compare/v2.1.1...v2.1.2) - 2023-02-18
|
||||
|
||||
|
||||
|
||||
@ -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: {
|
||||
|
||||
2030
package-lock.json
generated
2030
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "lx-music-desktop",
|
||||
"version": "2.2.0-beta.6",
|
||||
"version": "2.2.1-beta",
|
||||
"description": "一个免费的音乐查找助手",
|
||||
"main": "./dist/main.js",
|
||||
"productName": "lx-music-desktop",
|
||||
@ -205,8 +205,8 @@
|
||||
},
|
||||
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.0",
|
||||
"@babel/eslint-parser": "^7.19.1",
|
||||
"@babel/core": "^7.21.3",
|
||||
"@babel/eslint-parser": "^7.21.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-modules-umd": "^7.18.6",
|
||||
@ -216,60 +216,60 @@
|
||||
"@types/better-sqlite3": "^7.6.3",
|
||||
"@types/needle": "^3.2.0",
|
||||
"@types/tunnel": "^0.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.54.1",
|
||||
"@typescript-eslint/parser": "^5.54.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.0",
|
||||
"@typescript-eslint/parser": "^5.57.0",
|
||||
"@volar/vue-language-plugin-pug": "^1.2.0",
|
||||
"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.0",
|
||||
"core-js": "^3.29.1",
|
||||
"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.2",
|
||||
"electron-builder": "^24.0.0",
|
||||
"electron": "^22.3.5",
|
||||
"electron-builder": "^24.1.2",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-to-chromium": "^1.4.328",
|
||||
"electron-updater": "^6.0.0",
|
||||
"eslint": "^8.36.0",
|
||||
"electron-to-chromium": "^1.4.345",
|
||||
"electron-updater": "^6.0.2",
|
||||
"eslint": "^8.37.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-config-standard-with-typescript": "^34.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.9.0",
|
||||
"eslint-plugin-vue": "^9.10.0",
|
||||
"eslint-webpack-plugin": "^4.0.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.1.0",
|
||||
"mini-css-extract-plugin": "^2.7.3",
|
||||
"mini-css-extract-plugin": "^2.7.5",
|
||||
"node-loader": "^2.0.0",
|
||||
"postcss": "^8.4.21",
|
||||
"postcss-loader": "^7.0.2",
|
||||
"postcss-loader": "^7.1.0",
|
||||
"postcss-pxtorem": "^6.0.0",
|
||||
"pug": "^3.0.2",
|
||||
"pug-plain-loader": "^1.1.0",
|
||||
"rimraf": "^4.4.0",
|
||||
"rimraf": "^4.4.1",
|
||||
"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.6",
|
||||
"terser": "^5.16.8",
|
||||
"terser-webpack-plugin": "^5.3.7",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.5",
|
||||
"vue-eslint-parser": "^9.1.0",
|
||||
"typescript": "^5.0.2",
|
||||
"vue-eslint-parser": "^9.1.1",
|
||||
"vue-loader": "^17.0.1",
|
||||
"vue-template-compiler": "^2.7.14",
|
||||
"webpack": "^5.76.1",
|
||||
"webpack": "^5.77.0",
|
||||
"webpack-cli": "^5.0.1",
|
||||
"webpack-dev-server": "^4.11.1",
|
||||
"webpack-dev-server": "^4.13.1",
|
||||
"webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
@ -287,7 +287,7 @@
|
||||
"jschardet": "^3.0.0",
|
||||
"koa": "^2.14.1",
|
||||
"long": "^5.2.1",
|
||||
"music-metadata": "^8.1.3",
|
||||
"music-metadata": "^8.1.4",
|
||||
"needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060",
|
||||
"node-id3": "^0.2.6",
|
||||
"sortablejs": "^1.15.0",
|
||||
|
||||
@ -1,30 +1,12 @@
|
||||
从v2.2.0起,我们发布了一个独立版的[数据同步服务](https://github.com/lyswhut/lx-music-sync-server#readme),如果你有服务器,可以将其部署到服务器上作为私人多端同步服务使用,详情看该项目说明
|
||||
|
||||
### 不兼容性变更说明
|
||||
|
||||
- 同步功能,从这个版本起,数据同步功能至少需要移动端v1.0.0的版本才能连接
|
||||
|
||||
### 新增
|
||||
|
||||
- 重构数据同步功能,新增客户端模式
|
||||
- 新增全屏时自动关闭歌词设置,默认开启,可以去设置-桌面歌词设置更改
|
||||
- 新增设置-桌面歌词设置-重置窗口设置功能,点击时会重置桌面歌词窗口大小及位置
|
||||
- 新增设置-其他-列表数据清理功能,点击时会清空已创建的所有列表及所有收藏的歌曲
|
||||
|
||||
### 优化
|
||||
|
||||
- 支持wy源flac hires歌曲类型的显示
|
||||
- 快捷键调整音量时每次加减2%音量改为4%(#1220)
|
||||
- 音量、播放模式等设置弹出式按钮在鼠标移到按钮上时将自动弹出设置内容,保留点击切换显示/隐藏
|
||||
- 支持kg源搜索列表、排行榜flac hires歌曲类型的显示(#1231, #1238 By @helloplhm-qwq, @Folltoshe)
|
||||
- 优化对系统Media Session的支持,现在切歌不会再会导致信息丢失的问题了
|
||||
- 启用桌面歌词时,取消对歌词窗口的聚焦
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复同步连接的处理问题
|
||||
- 修复记住播放进度的情况下,使用Scheme URL打开应用播放的歌曲进度没有被重置的问题
|
||||
- 修复使用酷狗码无法打开某些类型的歌单的问题
|
||||
- 修复tx源某些歌单因为歌曲信息缺失导致打开失败的问题
|
||||
- 修复启用全局快捷键时与Media Session注册冲突的问题,启用全局快捷键时,不再注册媒体控制快捷键
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新 Electron 到v22.3.2
|
||||
- 更新 electron 到 v22.3.5
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -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,
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { join } from 'path'
|
||||
import { homedir } from 'os'
|
||||
|
||||
const isMac = process.platform == 'darwin'
|
||||
|
||||
const defaultSetting: LX.AppSetting = {
|
||||
version: '2.1.0',
|
||||
|
||||
@ -14,7 +16,7 @@ const defaultSetting: LX.AppSetting = {
|
||||
'common.isShowAnimation': true,
|
||||
'common.randomAnimate': true,
|
||||
'common.isAgreePact': false,
|
||||
'common.controlBtnPosition': process.platform === 'darwin' ? 'left' : 'right',
|
||||
'common.controlBtnPosition': isMac ? 'left' : 'right',
|
||||
'common.playBarProgressStyle': 'mini',
|
||||
'common.tryAutoUpdate': true,
|
||||
'common.showChangeLog': true,
|
||||
@ -31,7 +33,7 @@ const defaultSetting: LX.AppSetting = {
|
||||
'player.isShowLyricTranslation': false,
|
||||
'player.isShowLyricRoma': false,
|
||||
'player.isS2t': false,
|
||||
'player.isPlayLxlrc': false,
|
||||
'player.isPlayLxlrc': !isMac,
|
||||
'player.isSavePlayTime': false,
|
||||
'player.audioVisualization': false,
|
||||
'player.waitPlayEndStop': true,
|
||||
|
||||
@ -310,10 +310,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 +331,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 +341,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",
|
||||
@ -462,7 +465,7 @@
|
||||
"setting__sync": "Data synchronization",
|
||||
"setting__sync_client_address": "Current device address: {address}",
|
||||
"setting__sync_client_host": "Synchronization service address",
|
||||
"setting__sync_client_host_tip": "Please enter the synchronization service address",
|
||||
"setting__sync_client_host_tip": "http://IP:Port",
|
||||
"setting__sync_client_mode": "client mode",
|
||||
"setting__sync_client_status": "Status: {status}",
|
||||
"setting__sync_code_blocked_ip": "The IP of the current device has been blocked by the server!",
|
||||
|
||||
@ -465,7 +465,7 @@
|
||||
"setting__sync": "数据同步",
|
||||
"setting__sync_client_address": "当前设备地址:{address}",
|
||||
"setting__sync_client_host": "同步服务地址",
|
||||
"setting__sync_client_host_tip": "请输入同步服务地址",
|
||||
"setting__sync_client_host_tip": "http://IP地址:端口号",
|
||||
"setting__sync_client_mode": "客户端模式",
|
||||
"setting__sync_client_status": "状态:{status}",
|
||||
"setting__sync_code_blocked_ip": "当前设备的IP已被服务端封禁!",
|
||||
|
||||
@ -310,6 +310,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 +341,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": "歌詞下載",
|
||||
@ -463,7 +465,7 @@
|
||||
"setting__sync": "數據同步",
|
||||
"setting__sync_client_address": "當前設備地址:{address}",
|
||||
"setting__sync_client_host": "同步服務地址",
|
||||
"setting__sync_client_host_tip": "請輸入同步服務地址",
|
||||
"setting__sync_client_host_tip": "http://IP地址:端口號",
|
||||
"setting__sync_client_mode": "客戶端模式",
|
||||
"setting__sync_client_status": "狀態:{status}",
|
||||
"setting__sync_code_blocked_ip": "當前設備的IP已被服務端封禁!",
|
||||
|
||||
@ -61,6 +61,7 @@ const codeAuth = async(urlInfo: LX.Sync.Client.UrlInfo, serverId: string, authCo
|
||||
|
||||
const keyAuth = async(urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.ClientKeyInfo) => {
|
||||
const msg = aesEncrypt(SYNC_CODE.authMsg + getComputerName(), keyInfo.key)
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
return request(`${urlInfo.httpProtocol}//${urlInfo.hostPath}/ah`, { headers: { i: keyInfo.clientId, m: msg } }).then(({ text, code }) => {
|
||||
if (code != 200) throw new Error(SYNC_CODE.authFailed)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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.
@ -8,10 +8,10 @@
|
||||
<template #content>
|
||||
<div :class="$style.setting">
|
||||
<div :class="$style.info">
|
||||
<span>{{ playbackRate }}x</span>
|
||||
<base-btn min @click="handleUpdatePlaybackRate(1)">{{ $t('player__playback_rate_reset_btn') }}</base-btn>
|
||||
<span>{{ playbackRate.toFixed(2) }}x</span>
|
||||
<base-btn min @click="handleUpdatePlaybackRate(100)">{{ $t('player__playback_rate_reset_btn') }}</base-btn>
|
||||
</div>
|
||||
<base-slider-bar :value="playbackRate" :min="0.5" :max="2" @change="handleUpdatePlaybackRate" />
|
||||
<base-slider-bar :class="$style.slider" :value="playbackRate * 100" :min="60" :max="200" @change="handleUpdatePlaybackRate" />
|
||||
</div>
|
||||
</template>
|
||||
</material-popup-btn>
|
||||
@ -22,7 +22,7 @@
|
||||
import { playbackRate } from '@renderer/store/player/playbackRate'
|
||||
|
||||
const handleUpdatePlaybackRate = (val) => {
|
||||
window.app_event.setPlaybackRate(Math.round(val * 10) / 10)
|
||||
window.app_event.setPlaybackRate(Math.round(val) / 100)
|
||||
}
|
||||
|
||||
// const icon = computed(() => {
|
||||
@ -87,6 +87,7 @@ const handleUpdatePlaybackRate = (val) => {
|
||||
flex-flow: column nowrap;
|
||||
padding: 2px 3px;
|
||||
gap: 8px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.info {
|
||||
@ -100,5 +101,9 @@ const handleUpdatePlaybackRate = (val) => {
|
||||
}
|
||||
}
|
||||
|
||||
.slider {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
@update:model-value="saveVolumeIsMute($event)"
|
||||
/>
|
||||
</div>
|
||||
<base-slider-bar :value="volume" :min="0" :max="1" @change="handleUpdateVolume" />
|
||||
<base-slider-bar :class="$style.slider" :value="volume" :min="0" :max="1" @change="handleUpdateVolume" />
|
||||
</div>
|
||||
</template>
|
||||
</material-popup-btn>
|
||||
@ -91,6 +91,7 @@ const icon = computed(() => {
|
||||
flex-flow: column nowrap;
|
||||
padding: 2px 3px;
|
||||
gap: 8px;
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.info {
|
||||
@ -104,5 +105,8 @@ const icon = computed(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.slider {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted } from '@common/utils/vueTools'
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
import usePlayProgress from '@renderer/utils/compositions/usePlayProgress'
|
||||
import { isShowPlayerDetail } from '@renderer/store/player/state'
|
||||
|
||||
@ -69,12 +69,12 @@ export default {
|
||||
visibleProgress.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
visible.value = true
|
||||
requestAnimationFrame(() => {
|
||||
visible.value = false
|
||||
})
|
||||
})
|
||||
// onMounted(() => {
|
||||
// visible.value = true
|
||||
// requestAnimationFrame(() => {
|
||||
// visible.value = false
|
||||
// })
|
||||
// })
|
||||
|
||||
return {
|
||||
visible,
|
||||
|
||||
@ -74,13 +74,14 @@ export default ({ props }: {
|
||||
removeAllSelect()
|
||||
if (lastSelectIndex != clickIndex) {
|
||||
let isNeedReverse = false
|
||||
if (clickIndex < lastSelectIndex) {
|
||||
let temp = lastSelectIndex
|
||||
lastSelectIndex = clickIndex
|
||||
let _lastSelectIndex = lastSelectIndex
|
||||
if (clickIndex < _lastSelectIndex) {
|
||||
let temp = _lastSelectIndex
|
||||
_lastSelectIndex = clickIndex
|
||||
clickIndex = temp
|
||||
isNeedReverse = true
|
||||
}
|
||||
selectedList.value = props.list.slice(lastSelectIndex, clickIndex + 1)
|
||||
selectedList.value = props.list.slice(_lastSelectIndex, clickIndex + 1)
|
||||
if (isNeedReverse) selectedList.value.reverse()
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -358,6 +358,7 @@ export const getOnlineOtherSourceLyricInfo = async({ musicInfos, onToggleSource,
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
retryedSource.includes(musicInfo.source)
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
return reqPromise.then((lyricInfo: LX.Music.LyricInfo) => {
|
||||
return existTimeExp.test(lyricInfo.lyric) ? {
|
||||
lyricInfo,
|
||||
@ -392,6 +393,7 @@ export const handleGetOnlineLyricInfo = async({ musicInfo, onToggleSource, isRef
|
||||
} catch (err) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
return reqPromise.then((lyricInfo: LX.Music.LyricInfo) => {
|
||||
return existTimeExp.test(lyricInfo.lyric) ? {
|
||||
musicInfo,
|
||||
|
||||
@ -72,6 +72,7 @@ const getMusicPlayUrl = async(musicInfo: LX.Music.MusicInfo | LX.Download.ListIt
|
||||
if (window.lx.isPlayedStop || diffCurrentMusicInfo(musicInfo)) return null
|
||||
|
||||
return url
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
}).catch(err => {
|
||||
// console.log('err', err.message)
|
||||
if (window.lx.isPlayedStop ||
|
||||
@ -195,6 +196,7 @@ const handlePlay = () => {
|
||||
*/
|
||||
export const playList = (listId: string, index: number) => {
|
||||
setPlayListId(listId)
|
||||
pause()
|
||||
setPlayMusicInfo(listId, getList(listId)[index])
|
||||
clearPlayedList()
|
||||
clearTempPlayeList()
|
||||
@ -217,6 +219,7 @@ export const playNext = async(isAutoToggle = false): Promise<void> => {
|
||||
if (tempPlayList.length) { // 如果稍后播放列表存在歌曲则直接播放改列表的歌曲
|
||||
const playMusicInfo = tempPlayList[0]
|
||||
removeTempPlayList(0)
|
||||
pause()
|
||||
setPlayMusicInfo(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay)
|
||||
handlePlay()
|
||||
return
|
||||
@ -257,6 +260,7 @@ export const playNext = async(isAutoToggle = false): Promise<void> => {
|
||||
|
||||
if (index < playedList.length) {
|
||||
const playMusicInfo = playedList[index]
|
||||
pause()
|
||||
setPlayMusicInfo(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay)
|
||||
handlePlay()
|
||||
return
|
||||
@ -311,6 +315,7 @@ export const playNext = async(isAutoToggle = false): Promise<void> => {
|
||||
isTempPlay: false,
|
||||
}
|
||||
|
||||
pause()
|
||||
setPlayMusicInfo(nextPlayMusicInfo.listId, nextPlayMusicInfo.musicInfo)
|
||||
handlePlay()
|
||||
}
|
||||
@ -353,6 +358,7 @@ export const playPrev = async(isAutoToggle = false): Promise<void> => {
|
||||
|
||||
if (index > -1) {
|
||||
const playMusicInfo = playedList[index]
|
||||
pause()
|
||||
setPlayMusicInfo(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay)
|
||||
handlePlay()
|
||||
return
|
||||
@ -407,6 +413,7 @@ export const playPrev = async(isAutoToggle = false): Promise<void> => {
|
||||
isTempPlay: false,
|
||||
}
|
||||
|
||||
pause()
|
||||
setPlayMusicInfo(nextPlayMusicInfo.listId, nextPlayMusicInfo.musicInfo)
|
||||
handlePlay()
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ export default () => {
|
||||
musicInfo: songInfo,
|
||||
},
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
}).then(res => {
|
||||
// console.log(res)
|
||||
if (!/^https?:/.test(res.data.url)) return Promise.reject(new Error('Get url failed'))
|
||||
|
||||
@ -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)
|
||||
})
|
||||
|
||||
@ -95,6 +95,7 @@ export default () => {
|
||||
versionInfoPromise = handleGetVersionInfo()
|
||||
}
|
||||
} else versionInfoPromise = handleGetVersionInfo()
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
void versionInfoPromise.then((result) => {
|
||||
versionInfo.reCheck = false
|
||||
|
||||
|
||||
@ -14,12 +14,13 @@ export const setListDetail = (result: ListDetailInfo, id: string, page: number)
|
||||
listDetailInfo.list = markRaw([...result.list])
|
||||
listDetailInfo.id = id
|
||||
listDetailInfo.source = result.source
|
||||
listDetailInfo.total = result.total
|
||||
if (page == 1 || (result.total && result.list.length)) listDetailInfo.total = result.total
|
||||
else listDetailInfo.total = result.limit * page
|
||||
listDetailInfo.limit = result.limit
|
||||
listDetailInfo.page = page
|
||||
|
||||
if (result.list.length) listDetailInfo.noItemLabel = ''
|
||||
else listDetailInfo.noItemLabel = window.i18n.t('no_item')
|
||||
else if (page == 1) listDetailInfo.noItemLabel = window.i18n.t('no_item')
|
||||
}
|
||||
export const clearListDetail = () => {
|
||||
listDetailInfo.list = []
|
||||
@ -80,6 +81,7 @@ export const getListDetailAll = async(id: string, isRefresh = false): Promise<LX
|
||||
return result
|
||||
}) ?? Promise.reject(new Error('source not found' + source))
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
return await loadData(bangId, 1).then((result: ListDetailInfo) => {
|
||||
if (result.total <= result.limit) return result.list
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ const saveSearchHistoryListThrottle = throttle((list: LX.List.SearchHistoryList)
|
||||
|
||||
|
||||
export const getHistoryList = async() => {
|
||||
if (isInitedSearchHistory) return
|
||||
if (isInitedSearchHistory || historyList.length) return
|
||||
historyList.push(...(await getSearchHistoryList() ?? []))
|
||||
isInitedSearchHistory = true
|
||||
}
|
||||
@ -28,6 +28,7 @@ export const addHistoryWord = async(word: string) => {
|
||||
if (!appSetting['search.isShowHistorySearch']) return
|
||||
if (!isInitedSearchHistory) await getHistoryList()
|
||||
let index = historyList.indexOf(word)
|
||||
if (index == 0) return
|
||||
if (index > -1) historyList.splice(index, 1)
|
||||
if (historyList.length >= 15) historyList.splice(14, historyList.length - 14)
|
||||
historyList.unshift(word)
|
||||
|
||||
@ -34,25 +34,27 @@ const handleSortList = (list: LX.Music.MusicInfo[], keyword: string) => {
|
||||
|
||||
const setLists = (results: SearchResult[], page: number, text: string): LX.Music.MusicInfo[] => {
|
||||
let pages = []
|
||||
let total = 0
|
||||
// let limit = 0
|
||||
let totals = []
|
||||
let limit = 0
|
||||
let list = []
|
||||
for (const source of results) {
|
||||
maxPages[source.source] = source.allPage
|
||||
limit = Math.max(source.limit, limit)
|
||||
if (source.allPage < page) continue
|
||||
list.push(...source.list)
|
||||
pages.push(source.allPage)
|
||||
total += source.total
|
||||
// limit = Math.max(source.limit, limit)
|
||||
totals.push(source.total)
|
||||
}
|
||||
list = deduplicationList(list.map(s => markRaw(toNewMusicInfo(s))))
|
||||
let listInfo = listInfos.all
|
||||
listInfo.maxPage = Math.max(...pages)
|
||||
listInfo.total = total
|
||||
listInfo.maxPage = Math.max(0, ...pages)
|
||||
const total = Math.max(0, ...totals)
|
||||
if (page == 1 || (total && list.length)) listInfo.total = total
|
||||
else listInfo.total = limit * page
|
||||
// listInfo.limit = limit
|
||||
listInfo.page = page
|
||||
listInfo.list = handleSortList(list, text)
|
||||
if (text && !list.length) listInfo.noItemLabel = window.i18n.t('no_item')
|
||||
if (text && !list.length && page == 1) listInfo.noItemLabel = window.i18n.t('no_item')
|
||||
else listInfo.noItemLabel = ''
|
||||
return listInfo.list
|
||||
}
|
||||
@ -61,11 +63,12 @@ const setList = (datas: SearchResult, page: number, text: string): LX.Music.Musi
|
||||
// console.log(datas.source, datas.list)
|
||||
let listInfo = listInfos[datas.source] as ListInfo
|
||||
listInfo.list = deduplicationList(datas.list.map(s => markRaw(toNewMusicInfo(s))))
|
||||
listInfo.total = datas.total
|
||||
if (page == 1 || (datas.total && datas.list.length)) listInfo.total = datas.total
|
||||
else listInfo.total = datas.limit * page
|
||||
listInfo.maxPage = datas.allPage
|
||||
listInfo.page = page
|
||||
listInfo.limit = datas.limit
|
||||
if (text && !datas.list.length) listInfo.noItemLabel = window.i18n.t('no_item')
|
||||
if (text && !datas.list.length && page == 1) listInfo.noItemLabel = window.i18n.t('no_item')
|
||||
else listInfo.noItemLabel = ''
|
||||
return listInfo.list
|
||||
}
|
||||
|
||||
@ -36,22 +36,24 @@ let maxTotals: Partial<Record<LX.OnlineSource, number>> = {
|
||||
}
|
||||
const setLists = (results: SearchResult[], page: number, text: string): ListInfoItem[] => {
|
||||
let totals = []
|
||||
// let limit = 0
|
||||
let limit = 0
|
||||
let list = []
|
||||
for (const source of results) {
|
||||
list.push(...source.list)
|
||||
totals.push(source.total)
|
||||
maxTotals[source.source] = source.total
|
||||
maxPages[source.source] = Math.ceil(source.total / source.limit)
|
||||
// limit = Math.max(source.limit, limit)
|
||||
limit = Math.max(source.limit, limit)
|
||||
}
|
||||
markRawList(list)
|
||||
|
||||
let listInfo = listInfos.all
|
||||
listInfo.total = Math.max(...totals)
|
||||
const total = Math.max(0, ...totals)
|
||||
if (page == 1 || (total && list.length)) listInfo.total = total
|
||||
else listInfo.total = limit * page
|
||||
listInfo.page = page
|
||||
listInfo.list = handleSortList(list, text)
|
||||
if (text && !list.length) listInfo.noItemLabel = window.i18n.t('no_item')
|
||||
if (text && !list.length && page == 1) listInfo.noItemLabel = window.i18n.t('no_item')
|
||||
else listInfo.noItemLabel = ''
|
||||
return listInfo.list
|
||||
}
|
||||
@ -60,10 +62,11 @@ const setList = (datas: SearchResult, page: number, text: string): ListInfoItem[
|
||||
// console.log(datas.source, datas.list)
|
||||
let listInfo = listInfos[datas.source] as SearchListInfo
|
||||
listInfo.list = markRawList(datas.list)
|
||||
listInfo.total = datas.total
|
||||
if (page == 1 || (datas.total && datas.list.length)) listInfo.total = datas.total
|
||||
else listInfo.total = datas.limit * page
|
||||
listInfo.page = page
|
||||
listInfo.limit = datas.limit
|
||||
if (text && !datas.list.length) listInfo.noItemLabel = window.i18n.t('no_item')
|
||||
if (text && !datas.list.length && page == 1) listInfo.noItemLabel = window.i18n.t('no_item')
|
||||
else listInfo.noItemLabel = ''
|
||||
return listInfo.list
|
||||
}
|
||||
|
||||
@ -33,25 +33,27 @@ export const clearList = () => {
|
||||
|
||||
export const setList = (result: ListInfo, tagId: string, sortId: string, page: number) => {
|
||||
listInfo.list = markRaw([...result.list])
|
||||
listInfo.total = result.total
|
||||
if (page == 1 || (result.total && result.list.length)) listInfo.total = result.total
|
||||
else listInfo.total = result.limit * page
|
||||
listInfo.limit = result.limit
|
||||
listInfo.page = page
|
||||
listInfo.source = result.source
|
||||
listInfo.tagId = tagId
|
||||
listInfo.sortId = sortId
|
||||
if (result.list.length) listInfo.noItemLabel = ''
|
||||
else listInfo.noItemLabel = window.i18n.t('no_item')
|
||||
else if (page == 1) listInfo.noItemLabel = window.i18n.t('no_item')
|
||||
}
|
||||
export const setListDetail = (result: ListDetailInfo, id: string, page: number) => {
|
||||
listDetailInfo.list = markRaw([...result.list])
|
||||
listDetailInfo.id = id
|
||||
listDetailInfo.source = result.source
|
||||
listDetailInfo.total = result.total
|
||||
if (page == 1 || (result.total && result.list.length)) listDetailInfo.total = result.total
|
||||
else listDetailInfo.total = result.limit * page
|
||||
listDetailInfo.limit = result.limit
|
||||
listDetailInfo.page = page
|
||||
listDetailInfo.info = markRaw({ ...result.info })
|
||||
if (result.list.length) listDetailInfo.noItemLabel = ''
|
||||
else listDetailInfo.noItemLabel = window.i18n.t('no_item')
|
||||
else if (page == 1) listDetailInfo.noItemLabel = window.i18n.t('no_item')
|
||||
}
|
||||
|
||||
export const setSelectListInfo = (info: ListInfoItem) => {
|
||||
@ -158,6 +160,7 @@ export const getListDetailAll = async(id: string, source: LX.OnlineSource, isRef
|
||||
return result
|
||||
}) ?? Promise.reject(new Error('source not found' + source))
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
return await loadData(id, 1).then((result: ListDetailInfo) => {
|
||||
if (result.total <= result.limit) return result.list
|
||||
|
||||
|
||||
@ -120,7 +120,7 @@ export default {
|
||||
if (item.filesize_high !== 0) {
|
||||
let size = sizeFormate(item.filesize_high)
|
||||
types.push({ type: 'flac24bit', size, hash: item.hash_high })
|
||||
_types.flac = {
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
hash: item.hash_high,
|
||||
}
|
||||
@ -190,6 +190,7 @@ export default {
|
||||
let total = body.data.total
|
||||
let limit = 100
|
||||
let listData = this.filterData(body.data.info)
|
||||
// console.log(listData)
|
||||
return {
|
||||
total,
|
||||
list: listData,
|
||||
|
||||
@ -53,7 +53,7 @@ export default {
|
||||
singer: decodeName(rawData.SingerName),
|
||||
name: decodeName(rawData.SongName),
|
||||
albumName: decodeName(rawData.AlbumName),
|
||||
albumId: rawData.Albumid,
|
||||
albumId: rawData.AlbumID,
|
||||
songmid: rawData.Audioid,
|
||||
source: 'kg',
|
||||
interval: formatPlayTime(rawData.Duration),
|
||||
|
||||
@ -305,7 +305,8 @@ export default {
|
||||
return `https://y.qq.com/n/ryqq/playlist/${id}`
|
||||
},
|
||||
|
||||
search(text, page, limit = 20) {
|
||||
search(text, page, limit = 20, retryNum = 0) {
|
||||
if (retryNum > 5) throw new Error('max retry')
|
||||
return httpFetch(`http://c.y.qq.com/soso/fcgi-bin/client_music_search_songlist?page_no=${page - 1}&num_per_page=${limit}&format=json&query=${encodeURIComponent(text)}&remoteplace=txt.yqq.playlist&inCharset=utf8&outCharset=utf-8`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)',
|
||||
@ -313,7 +314,7 @@ export default {
|
||||
},
|
||||
})
|
||||
.promise.then(({ body }) => {
|
||||
if (body.code != 0) throw new Error('filed')
|
||||
if (body.code != 0) return this.search(text, page, limit, ++retryNum)
|
||||
// console.log(body.data.list)
|
||||
return {
|
||||
list: body.data.list.map(item => {
|
||||
|
||||
@ -68,13 +68,14 @@ export default ({ list, listAll }) => {
|
||||
removeAllSelect()
|
||||
if (lastSelectIndex != clickIndex) {
|
||||
let isNeedReverse = false
|
||||
if (clickIndex < lastSelectIndex) {
|
||||
let temp = lastSelectIndex
|
||||
lastSelectIndex = clickIndex
|
||||
let _lastSelectIndex = lastSelectIndex
|
||||
if (clickIndex < _lastSelectIndex) {
|
||||
let temp = _lastSelectIndex
|
||||
_lastSelectIndex = clickIndex
|
||||
clickIndex = temp
|
||||
isNeedReverse = true
|
||||
}
|
||||
selectedList.value = list.value.slice(lastSelectIndex, clickIndex + 1)
|
||||
selectedList.value = list.value.slice(_lastSelectIndex, clickIndex + 1)
|
||||
if (isNeedReverse) selectedList.value.reverse()
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -67,14 +67,15 @@ export default ({ list }) => {
|
||||
if (selectedList.value.length) {
|
||||
removeAllSelect()
|
||||
if (lastSelectIndex != clickIndex) {
|
||||
let _lastSelectIndex = lastSelectIndex
|
||||
let isNeedReverse = false
|
||||
if (clickIndex < lastSelectIndex) {
|
||||
let temp = lastSelectIndex
|
||||
lastSelectIndex = clickIndex
|
||||
if (clickIndex < _lastSelectIndex) {
|
||||
let temp = _lastSelectIndex
|
||||
_lastSelectIndex = clickIndex
|
||||
clickIndex = temp
|
||||
isNeedReverse = true
|
||||
}
|
||||
selectedList.value = list.value.slice(lastSelectIndex, clickIndex + 1)
|
||||
selectedList.value = list.value.slice(_lastSelectIndex, clickIndex + 1)
|
||||
if (isNeedReverse) selectedList.value.reverse()
|
||||
}
|
||||
} else {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user