mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-16 13:17:46 +00:00
feat: 支持 UNM rust
This commit is contained in:
parent
4d59401549
commit
4d54060a4f
29 changed files with 717 additions and 231 deletions
|
|
@ -41,6 +41,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/node": "^6.19.7",
|
"@sentry/node": "^6.19.7",
|
||||||
"@sentry/tracing": "^6.19.7",
|
"@sentry/tracing": "^6.19.7",
|
||||||
|
"@unblockneteasemusic/rust-napi": "^0.3.0-pre.1",
|
||||||
"NeteaseCloudMusicApi": "^4.5.12",
|
"NeteaseCloudMusicApi": "^4.5.12",
|
||||||
"better-sqlite3": "7.5.1",
|
"better-sqlite3": "7.5.1",
|
||||||
"change-case": "^4.1.2",
|
"change-case": "^4.1.2",
|
||||||
|
|
|
||||||
138
pnpm-lock.yaml
generated
138
pnpm-lock.yaml
generated
|
|
@ -22,6 +22,7 @@ specifiers:
|
||||||
'@types/react-dom': ^18.0.1
|
'@types/react-dom': ^18.0.1
|
||||||
'@typescript-eslint/eslint-plugin': ^5.21.0
|
'@typescript-eslint/eslint-plugin': ^5.21.0
|
||||||
'@typescript-eslint/parser': ^5.21.0
|
'@typescript-eslint/parser': ^5.21.0
|
||||||
|
'@unblockneteasemusic/rust-napi': ^0.3.0-pre.1
|
||||||
'@vitejs/plugin-react': ^1.3.1
|
'@vitejs/plugin-react': ^1.3.1
|
||||||
'@vitest/ui': ^0.10.0
|
'@vitest/ui': ^0.10.0
|
||||||
NeteaseCloudMusicApi: ^4.5.12
|
NeteaseCloudMusicApi: ^4.5.12
|
||||||
|
|
@ -88,6 +89,7 @@ specifiers:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sentry/node': 6.19.7
|
'@sentry/node': 6.19.7
|
||||||
'@sentry/tracing': 6.19.7
|
'@sentry/tracing': 6.19.7
|
||||||
|
'@unblockneteasemusic/rust-napi': 0.3.0-pre.1
|
||||||
NeteaseCloudMusicApi: 4.5.12
|
NeteaseCloudMusicApi: 4.5.12
|
||||||
better-sqlite3: 7.5.1
|
better-sqlite3: 7.5.1
|
||||||
change-case: 4.1.2
|
change-case: 4.1.2
|
||||||
|
|
@ -1212,6 +1214,142 @@ packages:
|
||||||
eslint-visitor-keys: 3.3.0
|
eslint-visitor-keys: 3.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@unblockneteasemusic/rust-napi-android-arm-eabi/0.3.0-pre.1:
|
||||||
|
resolution: {integrity: sha512-932T6uUSHbWXTS2lt0wTI5F2lsIrGea2aU22VwSFaHfpTgxB8qDfd+jn+zMRlpnqTmuglyd0hk/1yUZd9tgu+w==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@unblockneteasemusic/rust-napi-android-arm64/0.3.0-pre.1:
|
||||||
|
resolution: {integrity: sha512-RQMCzO7+0Iw+R/MHy0hvv9Vg6BzqrUmWk9bMLR0mkkYKxR0wPEaB7WpAvUfLRKevSqiWP8rrNRuzqGVBu0PaCg==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@unblockneteasemusic/rust-napi-darwin-arm64/0.3.0-pre.1:
|
||||||
|
resolution: {integrity: sha512-M3YvPhYNyBSytho3FmyX1cj5k21ZlW14mPuy/5oLRw4qehAmjsSYjCEFLG5I29IlZTLN0sbIz92dqHkYclSXSg==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@unblockneteasemusic/rust-napi-darwin-x64/0.3.0-pre.1:
|
||||||
|
resolution: {integrity: sha512-kN4Bur22hFo2UAJ4vljuEX4ue7TlhhOnz9Q3KrwhxOtv2KlQi2iQ/8tCl+/whKpqgf/cs/klQLDJj73PsE1G+w==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@unblockneteasemusic/rust-napi-freebsd-x64/0.3.0-pre.1:
|
||||||
|
resolution: {integrity: sha512-tRBudpZX+0X8sDSP+LmnU9nfsT7939rCu+bZhizjHHe2jt02iX/ZLHOkEcVBh0VHhHVvTehj0zH3iHFkfYnR2Q==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@unblockneteasemusic/rust-napi-linux-arm-gnueabihf/0.3.0-pre.1:
|
||||||
|
resolution: {integrity: sha512-3vkXlBm6f2dWOWLKaosTcFAO5b/VV9WvyT3PQBJFvq0PtRGonr2Zr1gYJC4zUz2UraSKaFg4GMKgopU2Duxgow==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@unblockneteasemusic/rust-napi-linux-arm64-gnu/0.3.0-pre.1:
|
||||||
|
resolution: {integrity: sha512-Zq1kjjXhle0OA7NzadzBQvjbTZfbK/qMuHay97+ZGXZH4uxv0jmJ2aQWR7HlrrKmQKpknURvrxbXmi8dxeI+SA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@unblockneteasemusic/rust-napi-linux-arm64-musl/0.3.0-pre.1:
|
||||||
|
resolution: {integrity: sha512-Yp7+Ra8ksx2nCZs18duK7BPtsY2chzdrBIrWY14N7aP0IIglwBcazP+GGFNaqqDx0nW+/0463pUsi8OgbWX+mA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@unblockneteasemusic/rust-napi-linux-x64-gnu/0.3.0-pre.1:
|
||||||
|
resolution: {integrity: sha512-A71/PhBCotAQPimGIJnZEYJwBv2FilhYC1OC4wOy3Rt54C9Cw12FJp49c7J13mZLktZfCJOSu6/6RPY8+6Yfrw==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@unblockneteasemusic/rust-napi-linux-x64-musl/0.3.0-pre.1:
|
||||||
|
resolution: {integrity: sha512-klXwwdVb4LdHmUrdclZSfn6nQwXddBwJJk392wRagjGUyNbUkC9b3JHfMEdrssMIPtIGtNHWt/43z+saovZl2g==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@unblockneteasemusic/rust-napi-win32-arm64-msvc/0.3.0-pre.1:
|
||||||
|
resolution: {integrity: sha512-jaX6UvQlRuH1iyextG34l8b19MtVFTZZX8U34oW3d7rZxcas5ZitEHzd6XfjpHcTtkXSyhQxx+WjDiY2BQ+B3A==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@unblockneteasemusic/rust-napi-win32-ia32-msvc/0.3.0-pre.1:
|
||||||
|
resolution: {integrity: sha512-QgO05vQxxkU0+bfprxQMVLXxguI8N1ApPQCyAYNnvrKTQ/F6OjV+bQghlWaKwcIeve8zoYN1zgSHDyNj+0xrYA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@unblockneteasemusic/rust-napi-win32-x64-msvc/0.3.0-pre.1:
|
||||||
|
resolution: {integrity: sha512-6CI0YlQxHiU6vetwoAjYgBOFlWoTkLVUSd0tpEN9/5R7iExRUHdFdRfpXqPJzpYnAhZlGqAIslCayoNcf7vnQw==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@unblockneteasemusic/rust-napi/0.3.0-pre.1:
|
||||||
|
resolution: {integrity: sha512-n1zDJvy5OEEMPQdhTPARRRQLM4Tnvx9UGq0smVKWu6CjutK6rcSIVoxe4ADILzBOY3RCe5vuo9Qn4RUzKCeeWQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
optionalDependencies:
|
||||||
|
'@unblockneteasemusic/rust-napi-android-arm-eabi': 0.3.0-pre.1
|
||||||
|
'@unblockneteasemusic/rust-napi-android-arm64': 0.3.0-pre.1
|
||||||
|
'@unblockneteasemusic/rust-napi-darwin-arm64': 0.3.0-pre.1
|
||||||
|
'@unblockneteasemusic/rust-napi-darwin-x64': 0.3.0-pre.1
|
||||||
|
'@unblockneteasemusic/rust-napi-freebsd-x64': 0.3.0-pre.1
|
||||||
|
'@unblockneteasemusic/rust-napi-linux-arm-gnueabihf': 0.3.0-pre.1
|
||||||
|
'@unblockneteasemusic/rust-napi-linux-arm64-gnu': 0.3.0-pre.1
|
||||||
|
'@unblockneteasemusic/rust-napi-linux-arm64-musl': 0.3.0-pre.1
|
||||||
|
'@unblockneteasemusic/rust-napi-linux-x64-gnu': 0.3.0-pre.1
|
||||||
|
'@unblockneteasemusic/rust-napi-linux-x64-musl': 0.3.0-pre.1
|
||||||
|
'@unblockneteasemusic/rust-napi-win32-arm64-msvc': 0.3.0-pre.1
|
||||||
|
'@unblockneteasemusic/rust-napi-win32-ia32-msvc': 0.3.0-pre.1
|
||||||
|
'@unblockneteasemusic/rust-napi-win32-x64-msvc': 0.3.0-pre.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@vitejs/plugin-react/1.3.1:
|
/@vitejs/plugin-react/1.3.1:
|
||||||
resolution: {integrity: sha512-qQS8Y2fZCjo5YmDUplEXl3yn+aueiwxB7BaoQ4nWYJYR+Ai8NXPVLlkLobVMs5+DeyFyg9Lrz6zCzdX1opcvyw==}
|
resolution: {integrity: sha512-qQS8Y2fZCjo5YmDUplEXl3yn+aueiwxB7BaoQ4nWYJYR+Ai8NXPVLlkLobVMs5+DeyFyg9Lrz6zCzdX1opcvyw==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
const { colord } = require('colord')
|
const { colord } = require('colord')
|
||||||
const colors = require('tailwindcss/colors')
|
const colors = require('tailwindcss/colors')
|
||||||
|
|
||||||
const replaceBrandColorWithCssVar = () => {
|
const replaceBrandColorWithCSSVar = () => {
|
||||||
const blues = Object.entries(colors.blue).map(([key, value]) => {
|
const blues = Object.entries(colors.blue).map(([key, value]) => {
|
||||||
const c = colord(value).toRgb()
|
const c = colord(value).toRgb()
|
||||||
return {
|
return {
|
||||||
|
|
@ -11,7 +11,7 @@ const replaceBrandColorWithCssVar = () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
postcssPlugin: 'replaceBrandColorWithCssVar',
|
postcssPlugin: 'replaceBrandColorWithCSSVar',
|
||||||
Declaration(decl) {
|
Declaration(decl) {
|
||||||
let value = decl.value
|
let value = decl.value
|
||||||
blues.forEach(blue => {
|
blues.forEach(blue => {
|
||||||
|
|
@ -33,12 +33,12 @@ const replaceBrandColorWithCssVar = () => {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
replaceBrandColorWithCssVar.postcss = true
|
replaceBrandColorWithCSSVar.postcss = true
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: [
|
plugins: [
|
||||||
require('tailwindcss'),
|
require('tailwindcss'),
|
||||||
require('autoprefixer'),
|
require('autoprefixer'),
|
||||||
replaceBrandColorWithCssVar,
|
replaceBrandColorWithCSSVar,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ const options = {
|
||||||
'electron',
|
'electron',
|
||||||
'NeteaseCloudMusicApi',
|
'NeteaseCloudMusicApi',
|
||||||
'better-sqlite3',
|
'better-sqlite3',
|
||||||
|
'@unblockneteasemusic/rust-napi'
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,9 @@
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
const colors = require('tailwindcss/colors')
|
|
||||||
const { colord } = require('colord')
|
const { colord } = require('colord')
|
||||||
const prettier = require('prettier')
|
const prettier = require('prettier')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const prettierConfig = require('../prettier.config.js')
|
const prettierConfig = require('../prettier.config.js')
|
||||||
|
const pickedColors = require('./pickedColors.js')
|
||||||
const pickedColors = {
|
|
||||||
blue: colors.blue,
|
|
||||||
red: colors.red,
|
|
||||||
orange: colors.orange,
|
|
||||||
amber: colors.amber,
|
|
||||||
yellow: colors.yellow,
|
|
||||||
lime: colors.lime,
|
|
||||||
green: colors.green,
|
|
||||||
emerald: colors.emerald,
|
|
||||||
teal: colors.teal,
|
|
||||||
cyan: colors.cyan,
|
|
||||||
sky: colors.sky,
|
|
||||||
indigo: colors.indigo,
|
|
||||||
violet: colors.violet,
|
|
||||||
purple: colors.purple,
|
|
||||||
fuchsia: colors.fuchsia,
|
|
||||||
pink: colors.pink,
|
|
||||||
rose: colors.rose,
|
|
||||||
}
|
|
||||||
module.exports = pickedColors
|
|
||||||
|
|
||||||
const colorsCss = {}
|
const colorsCss = {}
|
||||||
Object.entries(pickedColors).forEach(([name, colors]) => {
|
Object.entries(pickedColors).forEach(([name, colors]) => {
|
||||||
|
|
@ -47,4 +26,3 @@ ${name === 'blue' ? ':root' : `[data-accent-color='${name}']`} {${color}
|
||||||
|
|
||||||
const formatted = prettier.format(css, { ...prettierConfig, parser: 'css' })
|
const formatted = prettier.format(css, { ...prettierConfig, parser: 'css' })
|
||||||
fs.writeFileSync('./src/renderer/styles/accentColor.scss', formatted)
|
fs.writeFileSync('./src/renderer/styles/accentColor.scss', formatted)
|
||||||
|
|
||||||
|
|
|
||||||
24
scripts/pickedColors.js
Normal file
24
scripts/pickedColors.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
const colors = require('tailwindcss/colors')
|
||||||
|
|
||||||
|
const pickedColors = {
|
||||||
|
blue: colors.blue,
|
||||||
|
red: colors.red,
|
||||||
|
orange: colors.orange,
|
||||||
|
amber: colors.amber,
|
||||||
|
yellow: colors.yellow,
|
||||||
|
lime: colors.lime,
|
||||||
|
green: colors.green,
|
||||||
|
emerald: colors.emerald,
|
||||||
|
teal: colors.teal,
|
||||||
|
cyan: colors.cyan,
|
||||||
|
sky: colors.sky,
|
||||||
|
indigo: colors.indigo,
|
||||||
|
violet: colors.violet,
|
||||||
|
purple: colors.purple,
|
||||||
|
fuchsia: colors.fuchsia,
|
||||||
|
pink: colors.pink,
|
||||||
|
rose: colors.rose,
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = pickedColors
|
||||||
|
|
@ -6,6 +6,7 @@ import log from './log'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import * as musicMetadata from 'music-metadata'
|
import * as musicMetadata from 'music-metadata'
|
||||||
import { APIs, APIsParams, APIsResponse } from '../shared/CacheAPIs'
|
import { APIs, APIsParams, APIsResponse } from '../shared/CacheAPIs'
|
||||||
|
import { TablesStructures } from './db'
|
||||||
|
|
||||||
class Cache {
|
class Cache {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -206,59 +207,6 @@ class Cache {
|
||||||
return cache
|
return cache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get audio cache if API is song/detail
|
|
||||||
if (api === APIs.SongUrl) {
|
|
||||||
const cache = db.find(Tables.Audio, Number(req.query.id))
|
|
||||||
if (!cache) return
|
|
||||||
|
|
||||||
const audioFileName = `${cache.id}-${cache.br}.${cache.type}`
|
|
||||||
|
|
||||||
const isAudioFileExists = fs.existsSync(
|
|
||||||
`${app.getPath('userData')}/audio_cache/${audioFileName}`
|
|
||||||
)
|
|
||||||
if (!isAudioFileExists) return
|
|
||||||
|
|
||||||
log.debug(`[cache] Audio cache hit for ${req.path}`)
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
source: cache.source,
|
|
||||||
id: cache.id,
|
|
||||||
url: `http://127.0.0.1:42710/yesplaymusic/audio/${audioFileName}`,
|
|
||||||
br: cache.br,
|
|
||||||
size: 0,
|
|
||||||
md5: '',
|
|
||||||
code: 200,
|
|
||||||
expi: 0,
|
|
||||||
type: cache.type,
|
|
||||||
gain: 0,
|
|
||||||
fee: 8,
|
|
||||||
uf: null,
|
|
||||||
payed: 0,
|
|
||||||
flag: 4,
|
|
||||||
canExtend: false,
|
|
||||||
freeTrialInfo: null,
|
|
||||||
level: 'standard',
|
|
||||||
encodeType: cache.type,
|
|
||||||
freeTrialPrivilege: {
|
|
||||||
resConsumable: false,
|
|
||||||
userConsumable: false,
|
|
||||||
listenType: null,
|
|
||||||
},
|
|
||||||
freeTimeTrialPrivilege: {
|
|
||||||
resConsumable: false,
|
|
||||||
userConsumable: false,
|
|
||||||
type: 0,
|
|
||||||
remainTime: 0,
|
|
||||||
},
|
|
||||||
urlSource: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
code: 200,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAudio(fileName: string, res: Response) {
|
getAudio(fileName: string, res: Response) {
|
||||||
|
|
@ -279,17 +227,17 @@ class Cache {
|
||||||
.status(206)
|
.status(206)
|
||||||
.setHeader('Accept-Ranges', 'bytes')
|
.setHeader('Accept-Ranges', 'bytes')
|
||||||
.setHeader('Connection', 'keep-alive')
|
.setHeader('Connection', 'keep-alive')
|
||||||
.setHeader('Content-Range', `bytes 0-${audio.byteLength - 1}/${audio.byteLength}`)
|
.setHeader(
|
||||||
|
'Content-Range',
|
||||||
|
`bytes 0-${audio.byteLength - 1}/${audio.byteLength}`
|
||||||
|
)
|
||||||
.send(audio)
|
.send(audio)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).send({ error })
|
res.status(500).send({ error })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAudio(
|
async setAudio(buffer: Buffer, { id, url }: { id: number; url: string }) {
|
||||||
buffer: Buffer,
|
|
||||||
{ id, source }: { id: number; source: string }
|
|
||||||
) {
|
|
||||||
const path = `${app.getPath('userData')}/audio_cache`
|
const path = `${app.getPath('userData')}/audio_cache`
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -299,16 +247,26 @@ class Cache {
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = await musicMetadata.parseBuffer(buffer)
|
const meta = await musicMetadata.parseBuffer(buffer)
|
||||||
const br = meta.format.bitrate
|
const br =
|
||||||
const type = {
|
meta?.format?.codec === 'OPUS' ? 165000 : meta.format.bitrate ?? 0
|
||||||
|
const type =
|
||||||
|
{
|
||||||
'MPEG 1 Layer 3': 'mp3',
|
'MPEG 1 Layer 3': 'mp3',
|
||||||
'Ogg Vorbis': 'ogg',
|
'Ogg Vorbis': 'ogg',
|
||||||
AAC: 'm4a',
|
AAC: 'm4a',
|
||||||
FLAC: 'flac',
|
FLAC: 'flac',
|
||||||
unknown: 'unknown',
|
OPUS: 'opus',
|
||||||
}[meta.format.codec ?? 'unknown']
|
}[meta.format.codec ?? ''] ?? 'unknown'
|
||||||
|
|
||||||
await fs.writeFile(`${path}/${id}-${br}.${type}`, buffer, error => {
|
let source: TablesStructures[Tables.Audio]['source'] = 'unknown'
|
||||||
|
if (url.includes('googlevideo.com')) source = 'youtube'
|
||||||
|
if (url.includes('126.net')) source = 'netease'
|
||||||
|
if (url.includes('migu.cn')) source = 'migu'
|
||||||
|
if (url.includes('kuwo.cn')) source = 'kuwo'
|
||||||
|
if (url.includes('bilivideo.com')) source = 'bilibili'
|
||||||
|
// TODO: missing kugou qq joox
|
||||||
|
|
||||||
|
fs.writeFile(`${path}/${id}-${br}.${type}`, buffer, error => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return log.error(`[cache] cacheAudio failed: ${error}`)
|
return log.error(`[cache] cacheAudio failed: ${error}`)
|
||||||
}
|
}
|
||||||
|
|
@ -317,9 +275,9 @@ class Cache {
|
||||||
db.upsert(Tables.Audio, {
|
db.upsert(Tables.Audio, {
|
||||||
id,
|
id,
|
||||||
br,
|
br,
|
||||||
type,
|
type: type as TablesStructures[Tables.Audio]['type'],
|
||||||
source,
|
source,
|
||||||
updateAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
})
|
})
|
||||||
|
|
||||||
log.info(`[cache] cacheAudio ${id}-${br}.${type}`)
|
log.info(`[cache] cacheAudio ${id}-${br}.${type}`)
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,17 @@ export interface TablesStructures {
|
||||||
[Tables.Audio]: {
|
[Tables.Audio]: {
|
||||||
id: number
|
id: number
|
||||||
br: number
|
br: number
|
||||||
type: 'mp3' | 'flac' | 'ogg' | 'wav' | 'm4a' | 'aac' | 'unknown'
|
type: 'mp3' | 'flac' | 'ogg' | 'wav' | 'm4a' | 'aac' | 'unknown' | 'opus'
|
||||||
source: 'netease' | 'migu' | 'kuwo' | 'kugou' | 'youtube'
|
source:
|
||||||
url: string
|
| 'unknown'
|
||||||
|
| 'netease'
|
||||||
|
| 'migu'
|
||||||
|
| 'kuwo'
|
||||||
|
| 'kugou'
|
||||||
|
| 'youtube'
|
||||||
|
| 'qq'
|
||||||
|
| 'bilibili'
|
||||||
|
| 'joox'
|
||||||
updatedAt: number
|
updatedAt: number
|
||||||
}
|
}
|
||||||
[Tables.CoverColor]: {
|
[Tables.CoverColor]: {
|
||||||
|
|
|
||||||
|
|
@ -15,19 +15,21 @@ import { initIpcMain } from './ipcMain'
|
||||||
import { createTray, YPMTray } from './tray'
|
import { createTray, YPMTray } from './tray'
|
||||||
import { IpcChannels } from '@/shared/IpcChannels'
|
import { IpcChannels } from '@/shared/IpcChannels'
|
||||||
import { createTaskbar, Thumbar } from './windowsTaskbar'
|
import { createTaskbar, Thumbar } from './windowsTaskbar'
|
||||||
|
import { Store as State, initialState } from '@/shared/store'
|
||||||
|
|
||||||
const isWindows = process.platform === 'win32'
|
const isWindows = process.platform === 'win32'
|
||||||
const isMac = process.platform === 'darwin'
|
const isMac = process.platform === 'darwin'
|
||||||
const isLinux = process.platform === 'linux'
|
const isLinux = process.platform === 'linux'
|
||||||
const isDev = process.env.NODE_ENV === 'development'
|
const isDev = process.env.NODE_ENV === 'development'
|
||||||
|
|
||||||
interface TypedElectronStore {
|
export interface TypedElectronStore {
|
||||||
window: {
|
window: {
|
||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
x?: number
|
x?: number
|
||||||
y?: number
|
y?: number
|
||||||
}
|
}
|
||||||
|
settings: State['settings']
|
||||||
}
|
}
|
||||||
|
|
||||||
class Main {
|
class Main {
|
||||||
|
|
@ -40,6 +42,7 @@ class Main {
|
||||||
width: 1440,
|
width: 1440,
|
||||||
height: 960,
|
height: 960,
|
||||||
},
|
},
|
||||||
|
settings: initialState.settings,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -65,7 +68,7 @@ class Main {
|
||||||
this.handleWindowEvents()
|
this.handleWindowEvents()
|
||||||
this.createTray()
|
this.createTray()
|
||||||
this.createThumbar()
|
this.createThumbar()
|
||||||
initIpcMain(this.win, this.tray, this.thumbar)
|
initIpcMain(this.win, this.tray, this.thumbar, this.store)
|
||||||
this.initDevTools()
|
this.initDevTools()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -129,6 +132,51 @@ class Main {
|
||||||
if (url.startsWith('https:')) shell.openExternal(url)
|
if (url.startsWith('https:')) shell.openExternal(url)
|
||||||
return { action: 'deny' }
|
return { action: 'deny' }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.disableCORS()
|
||||||
|
}
|
||||||
|
|
||||||
|
disableCORS() {
|
||||||
|
if (!this.win) return
|
||||||
|
function UpsertKeyValue(obj, keyToChange, value) {
|
||||||
|
const keyToChangeLower = keyToChange.toLowerCase()
|
||||||
|
for (const key of Object.keys(obj)) {
|
||||||
|
if (key.toLowerCase() === keyToChangeLower) {
|
||||||
|
// Reassign old key
|
||||||
|
obj[key] = value
|
||||||
|
// Done
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Insert at end instead
|
||||||
|
obj[keyToChange] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
this.win.webContents.session.webRequest.onBeforeSendHeaders(
|
||||||
|
(details, callback) => {
|
||||||
|
const { requestHeaders, url } = details
|
||||||
|
UpsertKeyValue(requestHeaders, 'Access-Control-Allow-Origin', ['*'])
|
||||||
|
|
||||||
|
if (url.includes('googlevideo.com')) {
|
||||||
|
requestHeaders['Sec-Fetch-Mode'] = 'no-cors'
|
||||||
|
requestHeaders['Sec-Fetch-Dest'] = 'audio'
|
||||||
|
requestHeaders['Range'] = 'bytes=0-'
|
||||||
|
}
|
||||||
|
|
||||||
|
callback({ requestHeaders })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
this.win.webContents.session.webRequest.onHeadersReceived(
|
||||||
|
(details, callback) => {
|
||||||
|
const { responseHeaders } = details
|
||||||
|
UpsertKeyValue(responseHeaders, 'Access-Control-Allow-Origin', ['*'])
|
||||||
|
UpsertKeyValue(responseHeaders, 'Access-Control-Allow-Headers', ['*'])
|
||||||
|
callback({
|
||||||
|
responseHeaders,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleWindowEvents() {
|
handleWindowEvents() {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import { IpcChannels, IpcChannelsParams } from '../shared/IpcChannels'
|
||||||
import cache from './cache'
|
import cache from './cache'
|
||||||
import log from './log'
|
import log from './log'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
import Store from 'electron-store'
|
||||||
|
import { TypedElectronStore } from './index'
|
||||||
import { APIs } from '../shared/CacheAPIs'
|
import { APIs } from '../shared/CacheAPIs'
|
||||||
import { YPMTray } from './tray'
|
import { YPMTray } from './tray'
|
||||||
import { Thumbar } from './windowsTaskbar'
|
import { Thumbar } from './windowsTaskbar'
|
||||||
|
|
@ -18,11 +20,14 @@ const on = <T extends keyof IpcChannelsParams>(
|
||||||
export function initIpcMain(
|
export function initIpcMain(
|
||||||
win: BrowserWindow | null,
|
win: BrowserWindow | null,
|
||||||
tray: YPMTray | null,
|
tray: YPMTray | null,
|
||||||
thumbar: Thumbar | null
|
thumbar: Thumbar | null,
|
||||||
|
store: Store<TypedElectronStore>
|
||||||
) {
|
) {
|
||||||
initWindowIpcMain(win)
|
initWindowIpcMain(win)
|
||||||
initTrayIpcMain(tray)
|
initTrayIpcMain(tray)
|
||||||
initTaskbarIpcMain(thumbar)
|
initTaskbarIpcMain(thumbar)
|
||||||
|
initStoreIpcMain(store)
|
||||||
|
initOtherIpcMain()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -51,9 +56,7 @@ function initWindowIpcMain(win: BrowserWindow | null) {
|
||||||
function initTrayIpcMain(tray: YPMTray | null) {
|
function initTrayIpcMain(tray: YPMTray | null) {
|
||||||
on(IpcChannels.SetTrayTooltip, (e, { text }) => tray?.setTooltip(text))
|
on(IpcChannels.SetTrayTooltip, (e, { text }) => tray?.setTooltip(text))
|
||||||
|
|
||||||
on(IpcChannels.Like, (e, { isLiked }) =>
|
on(IpcChannels.Like, (e, { isLiked }) => tray?.setLikeState(isLiked))
|
||||||
tray?.setLikeState(isLiked)
|
|
||||||
)
|
|
||||||
|
|
||||||
on(IpcChannels.Play, () => tray?.setPlayState(true))
|
on(IpcChannels.Play, () => tray?.setPlayState(true))
|
||||||
on(IpcChannels.Pause, () => tray?.setPlayState(false))
|
on(IpcChannels.Pause, () => tray?.setPlayState(false))
|
||||||
|
|
@ -70,6 +73,23 @@ function initTaskbarIpcMain(thumbar: Thumbar | null) {
|
||||||
on(IpcChannels.Pause, () => thumbar?.setPlayState(false))
|
on(IpcChannels.Pause, () => thumbar?.setPlayState(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理需要electron-store的事件
|
||||||
|
* @param {Store<TypedElectronStore>} store
|
||||||
|
*/
|
||||||
|
function initStoreIpcMain(store: Store<TypedElectronStore>) {
|
||||||
|
/**
|
||||||
|
* 同步设置到Main
|
||||||
|
*/
|
||||||
|
on(IpcChannels.SyncSettings, (event, settings) => {
|
||||||
|
store.set('settings', settings)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理其他事件
|
||||||
|
*/
|
||||||
|
function initOtherIpcMain() {
|
||||||
/**
|
/**
|
||||||
* 清除API缓存
|
* 清除API缓存
|
||||||
*/
|
*/
|
||||||
|
|
@ -119,12 +139,17 @@ if (process.env.NODE_ENV === 'development') {
|
||||||
tables.forEach(table => {
|
tables.forEach(table => {
|
||||||
const data = db.findAll(table)
|
const data = db.findAll(table)
|
||||||
|
|
||||||
fs.writeFile(`./tmp/${table}.json`, JSON.stringify(data), function (err) {
|
fs.writeFile(
|
||||||
|
`./tmp/${table}.json`,
|
||||||
|
JSON.stringify(data),
|
||||||
|
function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return console.log(err)
|
return console.log(err)
|
||||||
}
|
}
|
||||||
console.log('The file was saved!')
|
console.log('The file was saved!')
|
||||||
})
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ CREATE TABLE IF NOT EXISTS "AccountData" ("id" text NOT NULL,"json" text NOT NUL
|
||||||
CREATE TABLE IF NOT EXISTS "Album" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
CREATE TABLE IF NOT EXISTS "Album" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
||||||
CREATE TABLE IF NOT EXISTS "ArtistAlbum" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
CREATE TABLE IF NOT EXISTS "ArtistAlbum" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
||||||
CREATE TABLE IF NOT EXISTS "Artist" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
CREATE TABLE IF NOT EXISTS "Artist" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
||||||
CREATE TABLE IF NOT EXISTS "Audio" ("id" integer NOT NULL,"br" int NOT NULL,"type" text NOT NULL,"srouce" text NOT NULL,"updateAt" int NOT NULL, PRIMARY KEY (id));
|
CREATE TABLE IF NOT EXISTS "Audio" ("id" integer NOT NULL,"br" int NOT NULL,"type" text NOT NULL,"srouce" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
||||||
CREATE TABLE IF NOT EXISTS "Lyric" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" integer NOT NULL, PRIMARY KEY (id));
|
CREATE TABLE IF NOT EXISTS "Lyric" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" integer NOT NULL, PRIMARY KEY (id));
|
||||||
CREATE TABLE IF NOT EXISTS "Playlist" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
CREATE TABLE IF NOT EXISTS "Playlist" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
||||||
CREATE TABLE IF NOT EXISTS "Track" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
CREATE TABLE IF NOT EXISTS "Track" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,12 @@ import log from './log'
|
||||||
import cache from './cache'
|
import cache from './cache'
|
||||||
import fileUpload from 'express-fileupload'
|
import fileUpload from 'express-fileupload'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import fs from 'fs'
|
||||||
|
import { db, Tables } from 'db'
|
||||||
|
import { app } from 'electron'
|
||||||
|
import type { FetchAudioSourceResponse } from '@/shared/api/Track'
|
||||||
|
import UNM from '@unblockneteasemusic/rust-napi'
|
||||||
|
import { APIs as CacheAPIs } from '../shared/CacheAPIs'
|
||||||
|
|
||||||
const isDev = process.env.NODE_ENV === 'development'
|
const isDev = process.env.NODE_ENV === 'development'
|
||||||
const isProd = process.env.NODE_ENV === 'production'
|
const isProd = process.env.NODE_ENV === 'production'
|
||||||
|
|
@ -21,6 +27,7 @@ class Server {
|
||||||
log.info('[server] starting http server')
|
log.info('[server] starting http server')
|
||||||
this.app.use(cookieParser())
|
this.app.use(cookieParser())
|
||||||
this.app.use(fileUpload())
|
this.app.use(fileUpload())
|
||||||
|
this.getAudioUrlHandler()
|
||||||
this.neteaseHandler()
|
this.neteaseHandler()
|
||||||
this.cacheAudioHandler()
|
this.cacheAudioHandler()
|
||||||
this.serveStaticForProd()
|
this.serveStaticForProd()
|
||||||
|
|
@ -32,7 +39,9 @@ class Server {
|
||||||
const neteaseApi = require('NeteaseCloudMusicApi') as (params: any) => any[]
|
const neteaseApi = require('NeteaseCloudMusicApi') as (params: any) => any[]
|
||||||
|
|
||||||
Object.entries(neteaseApi).forEach(([name, handler]) => {
|
Object.entries(neteaseApi).forEach(([name, handler]) => {
|
||||||
if (['serveNcmApi', 'getModulesDefinitions'].includes(name)) return
|
if (['serveNcmApi', 'getModulesDefinitions', 'song_url'].includes(name)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
name = pathCase(name)
|
name = pathCase(name)
|
||||||
|
|
||||||
|
|
@ -71,6 +80,205 @@ class Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAudioUrlHandler() {
|
||||||
|
const getFromCache = (id: number) => {
|
||||||
|
// get from cache
|
||||||
|
const cache = db.find(Tables.Audio, id)
|
||||||
|
if (!cache) return
|
||||||
|
|
||||||
|
const audioFileName = `${cache.id}-${cache.br}.${cache.type}`
|
||||||
|
|
||||||
|
const isAudioFileExists = fs.existsSync(
|
||||||
|
`${app.getPath('userData')}/audio_cache/${audioFileName}`
|
||||||
|
)
|
||||||
|
if (!isAudioFileExists) return
|
||||||
|
|
||||||
|
log.debug(`[server] Audio cache hit for song/url`)
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
source: cache.source,
|
||||||
|
id: cache.id,
|
||||||
|
url: `http://127.0.0.1:42710/yesplaymusic/audio/${audioFileName}`,
|
||||||
|
br: cache.br,
|
||||||
|
size: 0,
|
||||||
|
md5: '',
|
||||||
|
code: 200,
|
||||||
|
expi: 0,
|
||||||
|
type: cache.type,
|
||||||
|
gain: 0,
|
||||||
|
fee: 8,
|
||||||
|
uf: null,
|
||||||
|
payed: 0,
|
||||||
|
flag: 4,
|
||||||
|
canExtend: false,
|
||||||
|
freeTrialInfo: null,
|
||||||
|
level: 'standard',
|
||||||
|
encodeType: cache.type,
|
||||||
|
freeTrialPrivilege: {
|
||||||
|
resConsumable: false,
|
||||||
|
userConsumable: false,
|
||||||
|
listenType: null,
|
||||||
|
},
|
||||||
|
freeTimeTrialPrivilege: {
|
||||||
|
resConsumable: false,
|
||||||
|
userConsumable: false,
|
||||||
|
type: 0,
|
||||||
|
remainTime: 0,
|
||||||
|
},
|
||||||
|
urlSource: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
code: 200,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFromNetease = async (
|
||||||
|
req: Request
|
||||||
|
): Promise<FetchAudioSourceResponse | undefined> => {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const getSongUrl = (require('NeteaseCloudMusicApi') as any).song_url
|
||||||
|
|
||||||
|
const result = await getSongUrl({ ...req.query, cookie: req.cookies })
|
||||||
|
|
||||||
|
return result.body
|
||||||
|
} catch (error: any) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unmExecutor = new UNM.Executor()
|
||||||
|
const getFromUNM = async (id: number, req: Request) => {
|
||||||
|
log.debug('[server] Fetching audio url from UNM')
|
||||||
|
let track: Track = cache.get(CacheAPIs.Track, { ids: String(id) })
|
||||||
|
?.songs?.[0]
|
||||||
|
if (!track) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const getSongDetail = (require('NeteaseCloudMusicApi') as any)
|
||||||
|
.song_detail
|
||||||
|
|
||||||
|
track = await getSongDetail({ ...req.query, cookie: req.cookies })
|
||||||
|
}
|
||||||
|
if (!track) return
|
||||||
|
|
||||||
|
const trackForUNM = {
|
||||||
|
id: String(track.id),
|
||||||
|
name: track.name,
|
||||||
|
duration: track.dt,
|
||||||
|
album: {
|
||||||
|
id: String(track.al.id),
|
||||||
|
name: track.al.name,
|
||||||
|
},
|
||||||
|
artists: [
|
||||||
|
...track.ar.map((a: Artist) => ({
|
||||||
|
id: String(a.id),
|
||||||
|
name: a.name,
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceList = ['ytdl']
|
||||||
|
const context = {}
|
||||||
|
const matchedAudio = await unmExecutor.search(
|
||||||
|
sourceList,
|
||||||
|
trackForUNM,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
const retrievedSong = await unmExecutor.retrieve(matchedAudio, context)
|
||||||
|
const source =
|
||||||
|
retrievedSong.source === 'ytdl' ? 'youtube' : retrievedSong.source
|
||||||
|
if (retrievedSong.url) {
|
||||||
|
return {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
source,
|
||||||
|
id,
|
||||||
|
url: retrievedSong.url,
|
||||||
|
br: 128000,
|
||||||
|
size: 0,
|
||||||
|
md5: '',
|
||||||
|
code: 200,
|
||||||
|
expi: 0,
|
||||||
|
type: 'unknown',
|
||||||
|
gain: 0,
|
||||||
|
fee: 8,
|
||||||
|
uf: null,
|
||||||
|
payed: 0,
|
||||||
|
flag: 4,
|
||||||
|
canExtend: false,
|
||||||
|
freeTrialInfo: null,
|
||||||
|
level: 'standard',
|
||||||
|
encodeType: 'unknown',
|
||||||
|
freeTrialPrivilege: {
|
||||||
|
resConsumable: false,
|
||||||
|
userConsumable: false,
|
||||||
|
listenType: null,
|
||||||
|
},
|
||||||
|
freeTimeTrialPrivilege: {
|
||||||
|
resConsumable: false,
|
||||||
|
userConsumable: false,
|
||||||
|
type: 0,
|
||||||
|
remainTime: 0,
|
||||||
|
},
|
||||||
|
urlSource: 0,
|
||||||
|
unm: {
|
||||||
|
source,
|
||||||
|
song: matchedAudio.song,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
code: 200,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handler = async (req: Request, res: Response) => {
|
||||||
|
const id = Number(req.query.id) || 0
|
||||||
|
if (id === 0) {
|
||||||
|
return res.status(400).send({
|
||||||
|
code: 400,
|
||||||
|
msg: 'id is required or id is invalid',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// const fromCache = await getFromCache(id)
|
||||||
|
// if (fromCache) {
|
||||||
|
// res.status(200).send(fromCache)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// } catch (error) {
|
||||||
|
// log.error(`[server] getFromCache failed: ${String(error)}`)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const fromNetease = await getFromNetease(req)
|
||||||
|
// if (fromNetease?.code === 200 && !fromNetease?.data?.[0].freeTrialInfo) {
|
||||||
|
// res.status(200).send(fromNetease)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fromUNM = await getFromUNM(id, req)
|
||||||
|
if (fromUNM) {
|
||||||
|
res.status(200).send(fromUNM)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log.error(`[server] getFromNetease failed: ${String(error)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (fromNetease?.data?.[0].freeTrialInfo) {
|
||||||
|
// fromNetease.data[0].url = ''
|
||||||
|
// }
|
||||||
|
|
||||||
|
// res.status(fromNetease?.code ?? 500).send(fromNetease)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.app.get('/netease/song/url', handler)
|
||||||
|
}
|
||||||
|
|
||||||
cacheAudioHandler() {
|
cacheAudioHandler() {
|
||||||
this.app.get(
|
this.app.get(
|
||||||
'/yesplaymusic/audio/:filename',
|
'/yesplaymusic/audio/:filename',
|
||||||
|
|
@ -103,8 +311,8 @@ class Server {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await cache.setAudio(req.files.file.data, {
|
await cache.setAudio(req.files.file.data, {
|
||||||
id: id,
|
id,
|
||||||
source: 'netease',
|
url: String(req.query.url) || '',
|
||||||
})
|
})
|
||||||
res.status(200).send('Audio cached!')
|
res.status(200).send('Audio cached!')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,7 @@ class ThumbarImpl implements Thumbar {
|
||||||
|
|
||||||
private _updateThumbarButtons(clear: boolean) {
|
private _updateThumbarButtons(clear: boolean) {
|
||||||
this._win.setThumbarButtons(
|
this._win.setThumbarButtons(
|
||||||
clear
|
clear ? [] : [this._previous, this._playOrPause, this._next]
|
||||||
? []
|
|
||||||
: [this._previous, this._playOrPause, this._next]
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ const request: AxiosInstance = axios.create({
|
||||||
|
|
||||||
export async function cacheAudio(id: number, audio: string) {
|
export async function cacheAudio(id: number, audio: string) {
|
||||||
const file = await axios.get(audio, { responseType: 'arraybuffer' })
|
const file = await axios.get(audio, { responseType: 'arraybuffer' })
|
||||||
if (file.status !== 200) return
|
if (file.status !== 200 && file.status !== 206) return
|
||||||
|
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
const blob = new Blob([file.data], { type: 'multipart/form-data' })
|
const blob = new Blob([file.data], { type: 'multipart/form-data' })
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import { lyricParser } from '@/renderer/utils/lyric'
|
||||||
|
|
||||||
const Lyric = ({ className }: { className?: string }) => {
|
const Lyric = ({ className }: { className?: string }) => {
|
||||||
// const ease = [0.5, 0.2, 0.2, 0.8]
|
// const ease = [0.5, 0.2, 0.2, 0.8]
|
||||||
console.log('rendering')
|
|
||||||
|
|
||||||
const playerSnapshot = useSnapshot(player)
|
const playerSnapshot = useSnapshot(player)
|
||||||
const track = useMemo(() => playerSnapshot.track, [playerSnapshot.track])
|
const track = useMemo(() => playerSnapshot.track, [playerSnapshot.track])
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,8 @@ import { player } from '@/renderer/store'
|
||||||
import { formatDuration } from '@/renderer/utils/common'
|
import { formatDuration } from '@/renderer/utils/common'
|
||||||
import { State as PlayerState } from '@/renderer/utils/player'
|
import { State as PlayerState } from '@/renderer/utils/player'
|
||||||
|
|
||||||
const enableRenderLog = true
|
|
||||||
|
|
||||||
const PlayOrPauseButtonInTrack = memo(
|
const PlayOrPauseButtonInTrack = memo(
|
||||||
({ isHighlight, trackID }: { isHighlight: boolean; trackID: number }) => {
|
({ isHighlight, trackID }: { isHighlight: boolean; trackID: number }) => {
|
||||||
if (enableRenderLog)
|
|
||||||
console.debug(`Rendering TracksAlbum.tsx PlayOrPauseButtonInTrack`)
|
|
||||||
|
|
||||||
const playerSnapshot = useSnapshot(player)
|
const playerSnapshot = useSnapshot(player)
|
||||||
const isPlaying = useMemo(
|
const isPlaying = useMemo(
|
||||||
() => playerSnapshot.state === PlayerState.Playing,
|
() => playerSnapshot.state === PlayerState.Playing,
|
||||||
|
|
@ -58,9 +53,6 @@ const Track = memo(
|
||||||
isHighlight?: boolean
|
isHighlight?: boolean
|
||||||
onClick: (e: React.MouseEvent<HTMLElement>, trackID: number) => void
|
onClick: (e: React.MouseEvent<HTMLElement>, trackID: number) => void
|
||||||
}) => {
|
}) => {
|
||||||
if (enableRenderLog)
|
|
||||||
console.debug(`Rendering TracksAlbum.tsx Track ${track.name}`)
|
|
||||||
|
|
||||||
const subtitle = useMemo(
|
const subtitle = useMemo(
|
||||||
() => track.tns?.at(0) ?? track.alia?.at(0),
|
() => track.tns?.at(0) ?? track.alia?.at(0),
|
||||||
[track.alia, track.tns]
|
[track.alia, track.tns]
|
||||||
|
|
|
||||||
|
|
@ -179,8 +179,6 @@ const TracksList = memo(
|
||||||
isSkeleton?: boolean
|
isSkeleton?: boolean
|
||||||
onTrackDoubleClick?: (trackID: number) => void
|
onTrackDoubleClick?: (trackID: number) => void
|
||||||
}) => {
|
}) => {
|
||||||
console.debug('Rendering TrackList.tsx TrackList')
|
|
||||||
|
|
||||||
// Fake data when isLoading is true
|
// Fake data when isLoading is true
|
||||||
const skeletonTracks: Track[] = new Array(12).fill({})
|
const skeletonTracks: Track[] = new Array(12).fill({})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { IpcChannelsParams, IpcChannelsReturns } from '@/shared/IpcChannels'
|
import { IpcChannelsParams, IpcChannelsReturns } from '@/shared/IpcChannels'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
|
||||||
const useIpcRenderer = <T extends keyof IpcChannelsParams>(
|
const useIpcRenderer = <T extends keyof IpcChannelsParams>(
|
||||||
channcel: T,
|
channcel: T,
|
||||||
listener: (event: any, value: IpcChannelsReturns[T]) => void
|
listener: (event: any, value: IpcChannelsReturns[T]) => void
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
import { player } from '@/renderer/store'
|
import { player } from '@/renderer/store'
|
||||||
import { IpcChannels, IpcChannelsReturns, IpcChannelsParams } from '@/shared/IpcChannels'
|
import {
|
||||||
|
IpcChannels,
|
||||||
|
IpcChannelsReturns,
|
||||||
|
IpcChannelsParams,
|
||||||
|
} from '@/shared/IpcChannels'
|
||||||
|
|
||||||
const on = <T extends keyof IpcChannelsParams>(
|
const on = <T extends keyof IpcChannelsParams>(
|
||||||
channel: T,
|
channel: T,
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,9 @@ const Header = ({
|
||||||
return formatDuration(duration, 'zh-CN', 'hh[hr] mm[min]')
|
return formatDuration(duration, 'zh-CN', 'hh[hr] mm[min]')
|
||||||
}, [album?.songs])
|
}, [album?.songs])
|
||||||
|
|
||||||
const [isCoverError, setCoverError] = useState(coverUrl.includes('3132508627578625'))
|
const [isCoverError, setCoverError] = useState(
|
||||||
|
coverUrl.includes('3132508627578625')
|
||||||
|
)
|
||||||
|
|
||||||
const { data: userAlbums } = useUserAlbums()
|
const { data: userAlbums } = useUserAlbums()
|
||||||
const isThisAlbumLiked = useMemo(() => {
|
const isThisAlbumLiked = useMemo(() => {
|
||||||
|
|
@ -136,7 +138,7 @@ const Header = ({
|
||||||
coverUrl && (
|
coverUrl && (
|
||||||
<img
|
<img
|
||||||
src={coverUrl}
|
src={coverUrl}
|
||||||
className='rounded-2xl border w-full border-b-0 border-black border-opacity-5 dark:border-white dark:border-opacity-5'
|
className='w-full rounded-2xl border border-b-0 border-black border-opacity-5 dark:border-white dark:border-opacity-5'
|
||||||
onError={() => setCoverError(true)}
|
onError={() => setCoverError(true)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,6 @@ import {
|
||||||
State as PlayerState,
|
State as PlayerState,
|
||||||
} from '@/renderer/utils/player'
|
} from '@/renderer/utils/player'
|
||||||
|
|
||||||
const enableRenderLog = true
|
|
||||||
|
|
||||||
const PlayButton = ({
|
const PlayButton = ({
|
||||||
playlist,
|
playlist,
|
||||||
handlePlay,
|
handlePlay,
|
||||||
|
|
@ -76,7 +74,6 @@ const Header = memo(
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
handlePlay: () => void
|
handlePlay: () => void
|
||||||
}) => {
|
}) => {
|
||||||
if (enableRenderLog) console.debug('Rendering Playlist.tsx Header')
|
|
||||||
const coverUrl = resizeImage(playlist?.coverImgUrl || '', 'lg')
|
const coverUrl = resizeImage(playlist?.coverImgUrl || '', 'lg')
|
||||||
|
|
||||||
const mutationLikeAPlaylist = useMutationLikeAPlaylist()
|
const mutationLikeAPlaylist = useMutationLikeAPlaylist()
|
||||||
|
|
@ -225,8 +222,6 @@ const Tracks = memo(
|
||||||
handlePlay: (trackID: number | null) => void
|
handlePlay: (trackID: number | null) => void
|
||||||
isLoadingPlaylist: boolean
|
isLoadingPlaylist: boolean
|
||||||
}) => {
|
}) => {
|
||||||
if (enableRenderLog) console.debug('Rendering Playlist.tsx Tracks')
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: tracksPages,
|
data: tracksPages,
|
||||||
hasNextPage,
|
hasNextPage,
|
||||||
|
|
@ -281,8 +276,6 @@ const Tracks = memo(
|
||||||
Tracks.displayName = 'Tracks'
|
Tracks.displayName = 'Tracks'
|
||||||
|
|
||||||
const Playlist = () => {
|
const Playlist = () => {
|
||||||
if (enableRenderLog) console.debug('Rendering Playlist.tsx Playlist')
|
|
||||||
|
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const { data: playlist, isLoading } = usePlaylist({
|
const { data: playlist, isLoading } = usePlaylist({
|
||||||
id: Number(params.id) || 0,
|
id: Number(params.id) || 0,
|
||||||
|
|
|
||||||
|
|
@ -35,10 +35,15 @@ const AccentColor = () => {
|
||||||
{Object.entries(colors).map(([color, bg]) => (
|
{Object.entries(colors).map(([color, bg]) => (
|
||||||
<div
|
<div
|
||||||
key={color}
|
key={color}
|
||||||
className={classNames(bg, 'mr-2.5 h-5 w-5 rounded-full flex items-center justify-center')}
|
className={classNames(
|
||||||
|
bg,
|
||||||
|
'mr-2.5 flex h-5 w-5 items-center justify-center rounded-full'
|
||||||
|
)}
|
||||||
onClick={() => changeColor(color)}
|
onClick={() => changeColor(color)}
|
||||||
>
|
>
|
||||||
{color === accentColor && <div className='bg-white h-1.5 w-1.5 rounded-full'></div>}
|
{color === accentColor && (
|
||||||
|
<div className='h-1.5 w-1.5 rounded-full bg-white'></div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -47,12 +52,12 @@ const AccentColor = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Theme = () => {
|
const Theme = () => {
|
||||||
return <div className='mt-4'>
|
return (
|
||||||
|
<div className='mt-4'>
|
||||||
<div className='mb-2 dark:text-white'>主题</div>
|
<div className='mb-2 dark:text-white'>主题</div>
|
||||||
<div>
|
<div></div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Appearance = () => {
|
const Appearance = () => {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import Avatar from '@/renderer/components/Avatar'
|
||||||
import SvgIcon from '@/renderer/components/SvgIcon'
|
import SvgIcon from '@/renderer/components/SvgIcon'
|
||||||
import useUser from '@/renderer/hooks/useUser'
|
import useUser from '@/renderer/hooks/useUser'
|
||||||
import Appearance from './Appearance'
|
import Appearance from './Appearance'
|
||||||
|
import UnblockNeteaseMusic from './UnblockNeteaseMusic'
|
||||||
|
|
||||||
const UserCard = () => {
|
const UserCard = () => {
|
||||||
const { data: user } = useUser()
|
const { data: user } = useUser()
|
||||||
|
|
@ -42,17 +43,30 @@ const UserCard = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = ({
|
||||||
const categories = ['外观', '播放', '歌词', '其他', '试验性功能']
|
activeCategory,
|
||||||
const active = '外观'
|
setActiveCategory,
|
||||||
|
}: {
|
||||||
|
activeCategory: string
|
||||||
|
setActiveCategory: (category: string) => void
|
||||||
|
}) => {
|
||||||
|
const categories = [
|
||||||
|
'外观',
|
||||||
|
'播放',
|
||||||
|
'歌词',
|
||||||
|
'其他',
|
||||||
|
'UnblockNeteaseMusic',
|
||||||
|
'试验性功能',
|
||||||
|
]
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{categories.map(category => (
|
{categories.map(category => (
|
||||||
<div
|
<div
|
||||||
key={category}
|
key={category}
|
||||||
|
onClick={() => setActiveCategory(category)}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'btn-hover-animation my-px flex cursor-default items-center justify-between rounded-lg px-3 py-2 font-medium transition-colors duration-200 after:scale-[0.97] after:bg-black/[.06] dark:text-white dark:after:bg-white/10',
|
'btn-hover-animation my-px flex cursor-default items-center justify-between rounded-lg px-3 py-2 font-medium transition-colors duration-200 after:scale-[0.97] after:bg-black/[.06] dark:text-white dark:after:bg-white/10',
|
||||||
active === category
|
activeCategory === category
|
||||||
? 'text-black after:scale-100 after:opacity-100'
|
? 'text-black after:scale-100 after:opacity-100'
|
||||||
: 'text-gray-600'
|
: 'text-gray-600'
|
||||||
)}
|
)}
|
||||||
|
|
@ -65,14 +79,20 @@ const Sidebar = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Settings = () => {
|
const Settings = () => {
|
||||||
|
const [activeCategory, setActiveCategory] = useState('外观')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-6'>
|
<div className='mt-6'>
|
||||||
<UserCard />
|
<UserCard />
|
||||||
|
|
||||||
<div className='mt-8 grid grid-cols-[12rem_auto] gap-10'>
|
<div className='mt-8 grid grid-cols-[12rem_auto] gap-10'>
|
||||||
<Sidebar />
|
<Sidebar
|
||||||
|
activeCategory={activeCategory}
|
||||||
|
setActiveCategory={setActiveCategory}
|
||||||
|
/>
|
||||||
<div className=''>
|
<div className=''>
|
||||||
<Appearance />
|
{activeCategory === '外观' && <Appearance />}
|
||||||
|
{activeCategory === 'UnblockNeteaseMusic' && <UnblockNeteaseMusic />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
44
src/renderer/pages/Settings/UnblockNeteaseMusic.tsx
Normal file
44
src/renderer/pages/Settings/UnblockNeteaseMusic.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
const UnblockNeteaseMusic = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='text-xl font-medium text-gray-800 dark:text-white/70'>
|
||||||
|
UnblockNeteaseMusic
|
||||||
|
</div>
|
||||||
|
<div className='mt-3 h-px w-full bg-black/5 dark:bg-white/10'></div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
音源:
|
||||||
|
<div>
|
||||||
|
<input type='checkbox' id='migu' value='migu' />
|
||||||
|
<label htmlFor='migu'>migu</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type='checkbox' id='youtube' value='youtube' />
|
||||||
|
<label htmlFor='youtube'>youtube</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type='checkbox' id='kugou' value='kugou' />
|
||||||
|
<label htmlFor='kugou'>kugou</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type='checkbox' id='kuwo' value='kuwo' />
|
||||||
|
<label htmlFor='kuwo'>kuwo</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type='checkbox' id='qq' value='qq' />
|
||||||
|
<label htmlFor='qq'>qq</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type='checkbox' id='bilibili' value='bilibili' />
|
||||||
|
<label htmlFor='bilibili'>bilibili</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type='checkbox' id='joox' value='joox' />
|
||||||
|
<label htmlFor='joox'>joox</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UnblockNeteaseMusic
|
||||||
|
|
@ -2,36 +2,26 @@ import { proxy, subscribe } from 'valtio'
|
||||||
import { devtools } from 'valtio/utils'
|
import { devtools } from 'valtio/utils'
|
||||||
import { Player } from '@/renderer/utils/player'
|
import { Player } from '@/renderer/utils/player'
|
||||||
import { merge } from 'lodash-es'
|
import { merge } from 'lodash-es'
|
||||||
|
import { IpcChannels } from '@/shared/IpcChannels'
|
||||||
interface Store {
|
import { Store, initialState } from '@/shared/store'
|
||||||
uiStates: {
|
|
||||||
loginPhoneCountryCode: string
|
|
||||||
showLyricPanel: boolean
|
|
||||||
}
|
|
||||||
settings: {
|
|
||||||
showSidebar: boolean
|
|
||||||
accentColor: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState: Store = {
|
|
||||||
uiStates: {
|
|
||||||
loginPhoneCountryCode: '+86',
|
|
||||||
showLyricPanel: false,
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
showSidebar: true,
|
|
||||||
accentColor: 'blue',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const stateInLocalStorage = localStorage.getItem('state')
|
const stateInLocalStorage = localStorage.getItem('state')
|
||||||
export const state = proxy<Store>(
|
export const state = proxy<Store>(
|
||||||
merge(initialState, stateInLocalStorage ? JSON.parse(stateInLocalStorage) : {})
|
merge(initialState, [
|
||||||
|
stateInLocalStorage ? JSON.parse(stateInLocalStorage) : {},
|
||||||
|
{
|
||||||
|
uiStates: {
|
||||||
|
showLyricPanel: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
)
|
)
|
||||||
subscribe(state, () => {
|
subscribe(state, () => {
|
||||||
localStorage.setItem('state', JSON.stringify(state))
|
localStorage.setItem('state', JSON.stringify(state))
|
||||||
})
|
})
|
||||||
|
subscribe(state.settings, () => {
|
||||||
|
window.ipcRenderer?.send(IpcChannels.SyncSettings, { ...state.settings })
|
||||||
|
})
|
||||||
|
|
||||||
// player
|
// player
|
||||||
const playerInLocalStorage = localStorage.getItem('player')
|
const playerInLocalStorage = localStorage.getItem('player')
|
||||||
|
|
|
||||||
|
|
@ -224,15 +224,19 @@ export class Player {
|
||||||
}
|
}
|
||||||
if (this.trackID !== id) return
|
if (this.trackID !== id) return
|
||||||
Howler.unload()
|
Howler.unload()
|
||||||
|
const url = audio.includes('?')
|
||||||
|
? `${audio}&ypm-id=${id}`
|
||||||
|
: `${audio}?ypm-id=${id}`
|
||||||
const howler = new Howl({
|
const howler = new Howl({
|
||||||
src: [`${audio}?id=${id}`],
|
src: [url],
|
||||||
format: ['mp3', 'flac'],
|
format: ['mp3', 'flac', 'webm'],
|
||||||
html5: true,
|
html5: true,
|
||||||
autoplay,
|
autoplay,
|
||||||
volume: 1,
|
volume: 1,
|
||||||
onend: () => this._howlerOnEndCallback(),
|
onend: () => this._howlerOnEndCallback(),
|
||||||
})
|
})
|
||||||
_howler = howler
|
_howler = howler
|
||||||
|
window.howler = howler
|
||||||
if (autoplay) {
|
if (autoplay) {
|
||||||
this.play()
|
this.play()
|
||||||
this.state = State.Playing
|
this.state = State.Playing
|
||||||
|
|
@ -257,7 +261,7 @@ export class Player {
|
||||||
|
|
||||||
private _cacheAudio(audio: string) {
|
private _cacheAudio(audio: string) {
|
||||||
if (audio.includes('yesplaymusic')) return
|
if (audio.includes('yesplaymusic')) return
|
||||||
const id = Number(audio.split('?id=')[1])
|
const id = Number(new URL(audio).searchParams.get('ypm-id'))
|
||||||
if (isNaN(id) || !id) return
|
if (isNaN(id) || !id) return
|
||||||
cacheAudio(id, audio)
|
cacheAudio(id, audio)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { APIs } from './CacheAPIs'
|
import { APIs } from './CacheAPIs'
|
||||||
import { RepeatMode } from './playerDataTypes'
|
import { RepeatMode } from './playerDataTypes'
|
||||||
|
import { Store } from '@/shared/store'
|
||||||
|
|
||||||
export const enum IpcChannels {
|
export const enum IpcChannels {
|
||||||
ClearAPICache = 'clear-api-cache',
|
ClearAPICache = 'clear-api-cache',
|
||||||
|
|
@ -19,6 +20,7 @@ export const enum IpcChannels {
|
||||||
Previous = 'previous',
|
Previous = 'previous',
|
||||||
Like = 'like',
|
Like = 'like',
|
||||||
Repeat = 'repeat',
|
Repeat = 'repeat',
|
||||||
|
SyncSettings = 'sync-settings',
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipcMain.on params
|
// ipcMain.on params
|
||||||
|
|
@ -51,6 +53,7 @@ export interface IpcChannelsParams {
|
||||||
[IpcChannels.Repeat]: {
|
[IpcChannels.Repeat]: {
|
||||||
mode: RepeatMode
|
mode: RepeatMode
|
||||||
}
|
}
|
||||||
|
[IpcChannels.SyncSettings]: Store['settings']
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipcRenderer.on params
|
// ipcRenderer.on params
|
||||||
|
|
|
||||||
46
src/shared/store.ts
Normal file
46
src/shared/store.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
export interface Store {
|
||||||
|
uiStates: {
|
||||||
|
loginPhoneCountryCode: string
|
||||||
|
showLyricPanel: boolean
|
||||||
|
}
|
||||||
|
settings: {
|
||||||
|
showSidebar: boolean
|
||||||
|
accentColor: string
|
||||||
|
unm: {
|
||||||
|
enabled: boolean
|
||||||
|
sources: Array<
|
||||||
|
'migu' | 'kuwo' | 'kugou' | 'ytdl' | 'qq' | 'bilibili' | 'joox'
|
||||||
|
>
|
||||||
|
searchMode: 'order-first' | 'fast-first'
|
||||||
|
proxy: null | {
|
||||||
|
protocol: 'http' | 'https' | 'socks5'
|
||||||
|
host: string
|
||||||
|
port: number
|
||||||
|
username?: string
|
||||||
|
password?: string
|
||||||
|
}
|
||||||
|
cookies: {
|
||||||
|
qq?: string
|
||||||
|
joox?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialState: Store = {
|
||||||
|
uiStates: {
|
||||||
|
loginPhoneCountryCode: '+86',
|
||||||
|
showLyricPanel: false,
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
showSidebar: true,
|
||||||
|
accentColor: 'blue',
|
||||||
|
unm: {
|
||||||
|
enabled: true,
|
||||||
|
sources: ['migu'],
|
||||||
|
searchMode: 'order-first',
|
||||||
|
proxy: null,
|
||||||
|
cookies: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
const colors = require('tailwindcss/colors')
|
const colors = require('tailwindcss/colors')
|
||||||
const pickedColors = require('./scripts/generate.accent.color.css.js')
|
const pickedColors = require('./scripts/pickedColors.js')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: [
|
content: [
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue