mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-17 13:48:02 +00:00
feat: updates
This commit is contained in:
parent
884f3df41a
commit
c6c59b2cd9
84 changed files with 3531 additions and 2616 deletions
|
|
@ -3,7 +3,8 @@ import { app } from 'electron'
|
|||
import fs from 'fs'
|
||||
import SQLite3 from 'better-sqlite3'
|
||||
import log from './log'
|
||||
import { createFileIfNotExist, dirname, isProd } from './utils'
|
||||
import { createFileIfNotExist, dirname } from './utils'
|
||||
import { isProd } from './env'
|
||||
import pkg from '../../../package.json'
|
||||
import { compare, validate } from 'compare-versions'
|
||||
import os from 'os'
|
||||
|
|
|
|||
6
packages/desktop/main/env.ts
Normal file
6
packages/desktop/main/env.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export const isDev = process.env.NODE_ENV === 'development'
|
||||
export const isProd = process.env.NODE_ENV === 'production'
|
||||
export const isWindows = process.platform === 'win32'
|
||||
export const isMac = process.platform === 'darwin'
|
||||
export const isLinux = process.platform === 'linux'
|
||||
export const appName = 'R3Play'
|
||||
|
|
@ -1,12 +1,7 @@
|
|||
import './preload' // must be first
|
||||
import './sentry'
|
||||
import './server'
|
||||
import {
|
||||
BrowserWindow,
|
||||
BrowserWindowConstructorOptions,
|
||||
app,
|
||||
shell,
|
||||
} from 'electron'
|
||||
import { BrowserWindow, BrowserWindowConstructorOptions, app, shell } from 'electron'
|
||||
import { release } from 'os'
|
||||
import { join } from 'path'
|
||||
import log from './log'
|
||||
|
|
@ -15,7 +10,7 @@ import { createTray, YPMTray } from './tray'
|
|||
import { IpcChannels } from '@/shared/IpcChannels'
|
||||
import { createTaskbar, Thumbar } from './windowsTaskbar'
|
||||
import { createMenu } from './menu'
|
||||
import { isDev, isWindows, isLinux, isMac } from './utils'
|
||||
import { isDev, isWindows, isLinux, isMac, appName } from './env'
|
||||
import store from './store'
|
||||
// import './surrealdb'
|
||||
// import Airplay from './airplay'
|
||||
|
|
@ -81,7 +76,7 @@ class Main {
|
|||
|
||||
createWindow() {
|
||||
const options: BrowserWindowConstructorOptions = {
|
||||
title: 'YesPlayMusic',
|
||||
title: appName,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, 'rendererPreload.js'),
|
||||
},
|
||||
|
|
@ -93,6 +88,7 @@ class Main {
|
|||
trafficLightPosition: { x: 24, y: 24 },
|
||||
frame: false,
|
||||
transparent: true,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0)',
|
||||
show: false,
|
||||
}
|
||||
if (store.get('window')) {
|
||||
|
|
@ -132,35 +128,31 @@ class Main {
|
|||
return headers
|
||||
}
|
||||
|
||||
this.win.webContents.session.webRequest.onBeforeSendHeaders(
|
||||
(details, callback) => {
|
||||
const { requestHeaders, url } = details
|
||||
addCORSHeaders(requestHeaders)
|
||||
this.win.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
|
||||
const { requestHeaders, url } = details
|
||||
addCORSHeaders(requestHeaders)
|
||||
|
||||
// 不加这几个 header 的话,使用 axios 加载 YouTube 音频会很慢
|
||||
if (url.includes('googlevideo.com')) {
|
||||
requestHeaders['Sec-Fetch-Mode'] = 'no-cors'
|
||||
requestHeaders['Sec-Fetch-Dest'] = 'audio'
|
||||
requestHeaders['Range'] = 'bytes=0-'
|
||||
}
|
||||
|
||||
callback({ requestHeaders })
|
||||
// 不加这几个 header 的话,使用 axios 加载 YouTube 音频会很慢
|
||||
if (url.includes('googlevideo.com')) {
|
||||
requestHeaders['Sec-Fetch-Mode'] = 'no-cors'
|
||||
requestHeaders['Sec-Fetch-Dest'] = 'audio'
|
||||
requestHeaders['Range'] = 'bytes=0-'
|
||||
}
|
||||
)
|
||||
|
||||
this.win.webContents.session.webRequest.onHeadersReceived(
|
||||
(details, callback) => {
|
||||
const { responseHeaders, url } = details
|
||||
if (url.includes('sentry.io')) {
|
||||
callback({ responseHeaders })
|
||||
return
|
||||
}
|
||||
if (responseHeaders) {
|
||||
addCORSHeaders(responseHeaders)
|
||||
}
|
||||
callback({ requestHeaders })
|
||||
})
|
||||
|
||||
this.win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
|
||||
const { responseHeaders, url } = details
|
||||
if (url.includes('sentry.io')) {
|
||||
callback({ responseHeaders })
|
||||
return
|
||||
}
|
||||
)
|
||||
if (responseHeaders) {
|
||||
addCORSHeaders(responseHeaders)
|
||||
}
|
||||
callback({ responseHeaders })
|
||||
})
|
||||
}
|
||||
|
||||
handleWindowEvents() {
|
||||
|
|
@ -176,13 +168,11 @@ class Main {
|
|||
})
|
||||
|
||||
this.win.on('enter-full-screen', () => {
|
||||
this.win &&
|
||||
this.win.webContents.send(IpcChannels.FullscreenStateChange, true)
|
||||
this.win && this.win.webContents.send(IpcChannels.FullscreenStateChange, true)
|
||||
})
|
||||
|
||||
this.win.on('leave-full-screen', () => {
|
||||
this.win &&
|
||||
this.win.webContents.send(IpcChannels.FullscreenStateChange, false)
|
||||
this.win && this.win.webContents.send(IpcChannels.FullscreenStateChange, false)
|
||||
})
|
||||
|
||||
// Save window position
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import log from 'electron-log'
|
||||
import pc from 'picocolors'
|
||||
import { isDev } from './utils'
|
||||
import { isDev } from './env'
|
||||
|
||||
Object.assign(console, log.functions)
|
||||
log.variables.process = 'main'
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import {
|
|||
MenuItemConstructorOptions,
|
||||
shell,
|
||||
} from 'electron'
|
||||
import { logsPath, isMac } from './utils'
|
||||
import { isMac } from './env'
|
||||
import { logsPath } from './utils'
|
||||
import { exec } from 'child_process'
|
||||
|
||||
export const createMenu = (win: BrowserWindow) => {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import log from './log'
|
||||
import { app } from 'electron'
|
||||
import { isDev } from './env'
|
||||
import {
|
||||
createDirIfNotExist,
|
||||
devUserDataPath,
|
||||
isDev,
|
||||
portableUserDataPath,
|
||||
devUserDataPath,
|
||||
} from './utils'
|
||||
|
||||
if (isDev) {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
import { IpcChannels } from '@/shared/IpcChannels'
|
||||
import { isLinux, isMac, isProd, isWindows } from './utils'
|
||||
import { isLinux, isMac, isProd, isWindows } from './env'
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
|
||||
if (isProd) {
|
||||
const log = require('electron-log')
|
||||
log.transports.file.level = 'info'
|
||||
log.transports.ipc.level = false
|
||||
log.variables.process = 'renderer'
|
||||
contextBridge.exposeInMainWorld('log', log)
|
||||
}
|
||||
// if (isProd) {
|
||||
// const log = require('electron-log')
|
||||
// log.transports.file.level = 'info'
|
||||
// log.transports.ipc.level = false
|
||||
// log.variables.process = 'renderer'
|
||||
// contextBridge.exposeInMainWorld('log', log)
|
||||
// }
|
||||
|
||||
contextBridge.exposeInMainWorld('ipcRenderer', {
|
||||
invoke: ipcRenderer.invoke,
|
||||
|
|
@ -27,8 +27,7 @@ contextBridge.exposeInMainWorld('ipcRenderer', {
|
|||
|
||||
contextBridge.exposeInMainWorld('env', {
|
||||
isElectron: true,
|
||||
isEnableTitlebar:
|
||||
process.platform === 'win32' || process.platform === 'linux',
|
||||
isEnableTitlebar: process.platform === 'win32' || process.platform === 'linux',
|
||||
isLinux,
|
||||
isMac,
|
||||
isWindows,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import * as Sentry from '@sentry/electron'
|
||||
import pkg from '../../../package.json'
|
||||
import { appName } from './env'
|
||||
import log from './log'
|
||||
|
||||
log.info(`[sentry] sentry initializing`)
|
||||
|
||||
Sentry.init({
|
||||
dsn: 'https://2aaaa67f1c3d4d6baefafa5d58fcf340@o436528.ingest.sentry.io/6274637',
|
||||
release: `yesplaymusic@${pkg.version}`,
|
||||
release: `${appName}@${pkg.version}`,
|
||||
environment: process.env.NODE_ENV,
|
||||
|
||||
// Set tracesSampleRate to 1.0 to capture 100%
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import fs from 'fs'
|
|||
import { app } from 'electron'
|
||||
import type { FetchAudioSourceResponse } from '@/shared/api/Track'
|
||||
import { APIs as CacheAPIs } from '@/shared/CacheAPIs'
|
||||
import { isProd } from './utils'
|
||||
import { appName, isProd } from './env'
|
||||
import { APIs } from '@/shared/CacheAPIs'
|
||||
import history from 'connect-history-api-fallback'
|
||||
import { db, Tables } from './db'
|
||||
|
|
@ -19,7 +19,7 @@ class Server {
|
|||
port = Number(
|
||||
isProd
|
||||
? process.env.ELECTRON_WEB_SERVER_PORT || 42710
|
||||
: process.env.ELECTRON_DEV_NETEASE_API_PORT || 3000
|
||||
: process.env.ELECTRON_DEV_NETEASE_API_PORT || 30001
|
||||
)
|
||||
app = express()
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
|
|
@ -39,9 +39,7 @@ class Server {
|
|||
neteaseHandler() {
|
||||
Object.entries(this.netease).forEach(([name, handler]: [string, any]) => {
|
||||
// 例外处理
|
||||
if (
|
||||
['serveNcmApi', 'getModulesDefinitions', APIs.SongUrl].includes(name)
|
||||
) {
|
||||
if (['serveNcmApi', 'getModulesDefinitions', APIs.SongUrl].includes(name)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -103,7 +101,7 @@ class Server {
|
|||
{
|
||||
source: cache.source,
|
||||
id: cache.id,
|
||||
url: `http://127.0.0.1:42710/yesplaymusic/audio/${audioFileName}`,
|
||||
url: `http://127.0.0.1:42710/${appName.toLowerCase()}/audio/${audioFileName}`,
|
||||
br: cache.br,
|
||||
size: 0,
|
||||
md5: '',
|
||||
|
|
@ -137,9 +135,7 @@ class Server {
|
|||
}
|
||||
}
|
||||
|
||||
const getFromNetease = async (
|
||||
req: Request
|
||||
): Promise<FetchAudioSourceResponse | undefined> => {
|
||||
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
|
||||
|
|
@ -155,12 +151,10 @@ class Server {
|
|||
// 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]
|
||||
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
|
||||
const getSongDetail = (require('NeteaseCloudMusicApi') as any).song_detail
|
||||
|
||||
track = await getSongDetail({ ...req.query, cookie: req.cookies })
|
||||
}
|
||||
|
|
@ -184,14 +178,9 @@ class Server {
|
|||
|
||||
const sourceList = ['ytdl']
|
||||
const context = {}
|
||||
const matchedAudio = await unmExecutor.search(
|
||||
sourceList,
|
||||
trackForUNM,
|
||||
context
|
||||
)
|
||||
const matchedAudio = await unmExecutor.search(sourceList, trackForUNM, context)
|
||||
const retrievedSong = await unmExecutor.retrieve(matchedAudio, context)
|
||||
const source =
|
||||
retrievedSong.source === 'ytdl' ? 'youtube' : retrievedSong.source
|
||||
const source = retrievedSong.source === 'ytdl' ? 'youtube' : retrievedSong.source
|
||||
if (retrievedSong.url) {
|
||||
log.debug(
|
||||
`[server] UMN match: ${matchedAudio.song?.name} (https://youtube.com/v/${matchedAudio.song?.id})`
|
||||
|
|
@ -291,45 +280,38 @@ class Server {
|
|||
|
||||
cacheAudioHandler() {
|
||||
this.app.get(
|
||||
'/yesplaymusic/audio/:filename',
|
||||
`/${appName.toLowerCase()}/audio/:filename`,
|
||||
async (req: Request, res: Response) => {
|
||||
cache.getAudio(req.params.filename, res)
|
||||
}
|
||||
)
|
||||
this.app.post(
|
||||
'/yesplaymusic/audio/:id',
|
||||
async (req: Request, res: Response) => {
|
||||
const id = Number(req.params.id)
|
||||
const { url } = req.query
|
||||
if (isNaN(id)) {
|
||||
return res.status(400).send({ error: 'Invalid param id' })
|
||||
}
|
||||
if (!url) {
|
||||
return res.status(400).send({ error: 'Invalid query url' })
|
||||
}
|
||||
|
||||
if (
|
||||
!req.files ||
|
||||
Object.keys(req.files).length === 0 ||
|
||||
!req.files.file
|
||||
) {
|
||||
return res.status(400).send('No audio were uploaded.')
|
||||
}
|
||||
if ('length' in req.files.file) {
|
||||
return res.status(400).send('Only can upload one audio at a time.')
|
||||
}
|
||||
|
||||
try {
|
||||
await cache.setAudio(req.files.file.data, {
|
||||
id,
|
||||
url: String(req.query.url) || '',
|
||||
})
|
||||
res.status(200).send('Audio cached!')
|
||||
} catch (error) {
|
||||
res.status(500).send({ error })
|
||||
}
|
||||
this.app.post(`/${appName.toLowerCase()}/audio/:id`, async (req: Request, res: Response) => {
|
||||
const id = Number(req.params.id)
|
||||
const { url } = req.query
|
||||
if (isNaN(id)) {
|
||||
return res.status(400).send({ error: 'Invalid param id' })
|
||||
}
|
||||
)
|
||||
if (!url) {
|
||||
return res.status(400).send({ error: 'Invalid query url' })
|
||||
}
|
||||
|
||||
if (!req.files || Object.keys(req.files).length === 0 || !req.files.file) {
|
||||
return res.status(400).send('No audio were uploaded.')
|
||||
}
|
||||
if ('length' in req.files.file) {
|
||||
return res.status(400).send('Only can upload one audio at a time.')
|
||||
}
|
||||
|
||||
try {
|
||||
await cache.setAudio(req.files.file.data, {
|
||||
id,
|
||||
url: String(req.query.url) || '',
|
||||
})
|
||||
res.status(200).send('Audio cached!')
|
||||
} catch (error) {
|
||||
res.status(500).send({ error })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
listen() {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
} from 'electron'
|
||||
import { IpcChannels } from '@/shared/IpcChannels'
|
||||
import { RepeatMode } from '@/shared/playerDataTypes'
|
||||
import { appName } from './env'
|
||||
|
||||
const iconDirRoot =
|
||||
process.env.NODE_ENV === 'development'
|
||||
|
|
@ -134,7 +135,7 @@ class YPMTrayImpl implements YPMTray {
|
|||
this._contextMenu = Menu.buildFromTemplate(this._template)
|
||||
|
||||
this._updateContextMenu()
|
||||
this.setTooltip('YesPlayMusic')
|
||||
this.setTooltip(appName)
|
||||
|
||||
this._tray.on('click', () => win.show())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,13 @@ import fs from 'fs'
|
|||
import path from 'path'
|
||||
import os from 'os'
|
||||
import pkg from '../../../package.json'
|
||||
import { appName, isDev } from './env'
|
||||
|
||||
export const isDev = process.env.NODE_ENV === 'development'
|
||||
export const isProd = process.env.NODE_ENV === 'production'
|
||||
export const isWindows = process.platform === 'win32'
|
||||
export const isMac = process.platform === 'darwin'
|
||||
export const isLinux = process.platform === 'linux'
|
||||
export const dirname = isDev ? process.cwd() : __dirname
|
||||
export const devUserDataPath = path.resolve(process.cwd(), '../../tmp/userData')
|
||||
export const portableUserDataPath = path.resolve(
|
||||
process.env.PORTABLE_EXECUTABLE_DIR || '',
|
||||
'./YesPlayMusic-UserData'
|
||||
`./${appName.toLowerCase()}-UserData`
|
||||
)
|
||||
export const logsPath = {
|
||||
linux: `~/.config/${pkg.productName}/logs`,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue