feat: 支持GitHub Actions和一堆其他更新
|
|
@ -6,14 +6,23 @@ module.exports = {
|
||||||
appId: 'com.qier222.yesplaymusic',
|
appId: 'com.qier222.yesplaymusic',
|
||||||
productName: 'YesPlayMusic',
|
productName: 'YesPlayMusic',
|
||||||
copyright: 'Copyright © 2022 ${author}',
|
copyright: 'Copyright © 2022 ${author}',
|
||||||
asar: false,
|
asar: true,
|
||||||
directories: {
|
directories: {
|
||||||
output: 'release/${version}',
|
output: 'release',
|
||||||
buildResources: 'build',
|
buildResources: 'build',
|
||||||
},
|
},
|
||||||
npmRebuild: false,
|
npmRebuild: false,
|
||||||
buildDependenciesFromSource: true,
|
buildDependenciesFromSource: true,
|
||||||
files: ['dist'],
|
files: ['./dist'],
|
||||||
|
publish: [
|
||||||
|
{
|
||||||
|
provider: 'github',
|
||||||
|
owner: 'qier222',
|
||||||
|
repo: 'YesPlayMusic',
|
||||||
|
vPrefixedTagName: true,
|
||||||
|
releaseType: 'draft',
|
||||||
|
},
|
||||||
|
],
|
||||||
win: {
|
win: {
|
||||||
target: [
|
target: [
|
||||||
{
|
{
|
||||||
|
|
@ -28,8 +37,14 @@ module.exports = {
|
||||||
target: 'nsis',
|
target: 'nsis',
|
||||||
arch: ['ia32'],
|
arch: ['ia32'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
target: 'portable',
|
||||||
|
arch: ['x64'],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
artifactName: '${productName}-${version}-Setup.${ext}',
|
artifactName: '${productName}-${os}-${version}-${arch}-Setup.${ext}',
|
||||||
|
publisherName: 'qier222',
|
||||||
|
icon: 'build/icons/icon.ico',
|
||||||
},
|
},
|
||||||
nsis: {
|
nsis: {
|
||||||
oneClick: false,
|
oneClick: false,
|
||||||
|
|
@ -38,12 +53,49 @@ module.exports = {
|
||||||
deleteAppDataOnUninstall: true,
|
deleteAppDataOnUninstall: true,
|
||||||
},
|
},
|
||||||
mac: {
|
mac: {
|
||||||
target: ['dmg'],
|
target: [
|
||||||
artifactName: '${productName}-${version}-Installer.${ext}',
|
{
|
||||||
|
target: 'dmg',
|
||||||
|
arch: ['x64', 'arm64', 'universal'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
artifactName: '${productName}-${os}-${version}-${arch}.${ext}',
|
||||||
|
darkModeSupport: true,
|
||||||
|
category: 'public.app-category.music',
|
||||||
|
},
|
||||||
|
dmg: {
|
||||||
|
icon: 'build/icons/icon.icns',
|
||||||
},
|
},
|
||||||
linux: {
|
linux: {
|
||||||
target: ['AppImage'],
|
target: [
|
||||||
artifactName: '${productName}-${version}-Installer.${ext}',
|
{
|
||||||
|
target: 'deb',
|
||||||
|
arch: ['x64', 'arm64', 'armv7l'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'AppImage',
|
||||||
|
arch: ['x64'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'snap',
|
||||||
|
arch: ['x64'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'pacman',
|
||||||
|
arch: ['x64'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'rpm',
|
||||||
|
arch: ['x64'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'tar.gz',
|
||||||
|
arch: ['x64'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
artifactName: '${productName}-${os}-${version}.${ext}',
|
||||||
|
category: 'Music',
|
||||||
|
icon: './build/icon.icns',
|
||||||
},
|
},
|
||||||
files: [
|
files: [
|
||||||
'dist/main/**/*',
|
'dist/main/**/*',
|
||||||
|
|
|
||||||
90
.github/workflows/build.yaml
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
name: Build/Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- react
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [macos-latest, windows-latest, ubuntu-18.04]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out Git repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: 'recursive'
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v2.0.1
|
||||||
|
with:
|
||||||
|
version: 6.29.0
|
||||||
|
|
||||||
|
- name: Install Node.js 16
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: 16
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile false
|
||||||
|
|
||||||
|
- name: Electron rebuild
|
||||||
|
run: pnpm electron-rebuild
|
||||||
|
|
||||||
|
- name: Install RPM & Pacman (on Ubuntu)
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
run: |
|
||||||
|
sudo apt-get update &&
|
||||||
|
sudo apt-get install --no-install-recommends -y rpm &&
|
||||||
|
sudo apt-get install --no-install-recommends -y bsdtar &&
|
||||||
|
sudo apt-get install --no-install-recommends -y libopenjp2-tools
|
||||||
|
|
||||||
|
- name: Install Snapcraft (on Ubuntu)
|
||||||
|
uses: samuelmeuli/action-snapcraft@v1
|
||||||
|
if: startsWith(matrix.os, 'ubuntu')
|
||||||
|
# with:
|
||||||
|
# Disable since the Snapcraft token is currently not working
|
||||||
|
# snapcraft_token: ${{ secrets.snapcraft_token }}
|
||||||
|
|
||||||
|
- name: Build/Release Electron app
|
||||||
|
uses: njzydark/action-electron-builder-pnpm@v1.1.0-pnpm
|
||||||
|
env:
|
||||||
|
ELECTRON_WEB_SERVER_PORT: 42710
|
||||||
|
VITE_APP_NETEASE_API_URL: /netease
|
||||||
|
VITE_APP_LASTFM_API_KEY: 09c55292403d961aa517ff7f5e8a3d9c
|
||||||
|
VITE_APP_LASTFM_API_SHARED_SECRET: 307c9fda32b3904e53654baff215cb67
|
||||||
|
with:
|
||||||
|
# GitHub token, automatically provided to the action
|
||||||
|
# (No need to define this secret in the repo settings)
|
||||||
|
github_token: ${{ secrets.github_token }}
|
||||||
|
# If the commit is tagged with a version (e.g. "v1.0.0"),
|
||||||
|
# release the app after building
|
||||||
|
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
args: --config .electron-builder.config.js
|
||||||
|
skip_package_manager_install: true
|
||||||
|
package_manager: pnpm
|
||||||
|
|
||||||
|
- name: Upload Artifact (macOS)
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: YesPlayMusic-mac
|
||||||
|
path: release/*-universal.dmg
|
||||||
|
if-no-files-found: ignore
|
||||||
|
|
||||||
|
- name: Upload Artifact (Windows)
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: YesPlayMusic-win
|
||||||
|
path: release/*-Setup.exe
|
||||||
|
if-no-files-found: ignore
|
||||||
|
|
||||||
|
- name: Upload Artifact (Linux)
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: YesPlayMusic-linux
|
||||||
|
path: release/*.AppImage
|
||||||
|
if-no-files-found: ignore
|
||||||
BIN
build/icon.icns
BIN
build/icon.ico
|
Before Width: | Height: | Size: 36 KiB |
BIN
build/icons/1024x1024.png
Normal file
|
After Width: | Height: | Size: 523 KiB |
BIN
build/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
build/icons/16x16.png
Normal file
|
After Width: | Height: | Size: 474 B |
BIN
build/icons/24x24.png
Normal file
|
After Width: | Height: | Size: 750 B |
BIN
build/icons/256x256.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
build/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 965 B |
BIN
build/icons/48x48.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
build/icons/512x512.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
build/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
build/icons/icon.icns
Normal file
BIN
build/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 353 KiB |
BIN
build/icons/icon.png
Normal file
|
After Width: | Height: | Size: 276 KiB |
BIN
build/icons/menu@88.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
|
@ -14,13 +14,13 @@
|
||||||
"dev:renderer": "vite dev",
|
"dev:renderer": "vite dev",
|
||||||
"build:main": "node scripts/build.main.mjs",
|
"build:main": "node scripts/build.main.mjs",
|
||||||
"build:renderer": "vite build",
|
"build:renderer": "vite build",
|
||||||
"build": "npm run typecheck && cross-env-shell IS_ELECTRON=true npm run build:renderer && npm run build:main && electron-builder --config .electron-builder.config.js",
|
"build": "npm run typecheck && cross-env-shell IS_ELECTRON=true npm run build:renderer && npm run build:main",
|
||||||
"build-dir": "npm run typecheck && cross-env-shell IS_ELECTRON=true npm run build:renderer && npm run build:main && electron-builder --config .electron-builder.config.js --dir",
|
"build:app": "npm run build && electron-builder --config .electron-builder.config.js",
|
||||||
|
"build:app-dir": "npm run build && electron-builder --config .electron-builder.config.js --dir",
|
||||||
"typecheck": "tsc --noEmit --project src/renderer/tsconfig.json",
|
"typecheck": "tsc --noEmit --project src/renderer/tsconfig.json",
|
||||||
"debug": "cross-env-shell NODE_ENV=debug \"npm run typecheck && node scripts/build.mjs && vite ./src/renderer\"",
|
"debug": "cross-env-shell NODE_ENV=debug \"npm run typecheck && node scripts/build.mjs && vite ./src/renderer\"",
|
||||||
"eslint": "eslint --ext .ts,.js ./",
|
"eslint": "eslint --ext .ts,.js ./",
|
||||||
"prettier": "prettier --write './**/*.{ts,js,tsx,jsx}'",
|
"prettier": "prettier --write './**/*.{ts,js,tsx,jsx}'"
|
||||||
"postinstall": "electron-rebuild"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,10 @@ if (argv.watch) {
|
||||||
spinner.start()
|
spinner.start()
|
||||||
build({
|
build({
|
||||||
...options,
|
...options,
|
||||||
|
define: {
|
||||||
|
...options.define,
|
||||||
|
'process.env.NODE_ENV': '"production"',
|
||||||
|
},
|
||||||
minify: true,
|
minify: true,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import path from 'path'
|
||||||
logger.info('[server] starting http server')
|
logger.info('[server] starting http server')
|
||||||
|
|
||||||
const isDev = process.env.NODE_ENV === 'development'
|
const isDev = process.env.NODE_ENV === 'development'
|
||||||
|
const isProd = process.env.NODE_ENV === 'production'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const neteaseApi = require('NeteaseCloudMusicApi') as (params: any) => any[]
|
const neteaseApi = require('NeteaseCloudMusicApi') as (params: any) => any[]
|
||||||
|
|
@ -87,14 +88,14 @@ app.post('/yesplaymusic/audio/:id', async (req: Request, res: Response) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!isDev) {
|
if (isProd) {
|
||||||
app.use(express.static(path.join(__dirname, '../renderer')))
|
app.use('/', express.static(path.join(__dirname, '../renderer/')))
|
||||||
}
|
}
|
||||||
|
|
||||||
const port = Number(
|
const port = Number(
|
||||||
isDev
|
isProd
|
||||||
? process.env.ELECTRON_DEV_NETEASE_API_PORT ?? 3000
|
? process.env.ELECTRON_WEB_SERVER_PORT ?? 42710
|
||||||
: process.env.ELECTRON_WEB_SERVER_PORT ?? 42710
|
: process.env.ELECTRON_DEV_NETEASE_API_PORT ?? 3000
|
||||||
)
|
)
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
logger.info(`[server] API server listening on port ${port}`)
|
logger.info(`[server] API server listening on port ${port}`)
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,6 @@ const FMCard = () => {
|
||||||
if (coverUrl) {
|
if (coverUrl) {
|
||||||
average(coverUrl, { amount: 1, format: 'hex', sample: 1 }).then(color => {
|
average(coverUrl, { amount: 1, format: 'hex', sample: 1 }).then(color => {
|
||||||
let c = colord(color as string)
|
let c = colord(color as string)
|
||||||
console.log({ dark: c.isDark(), light: c.isLight() })
|
|
||||||
|
|
||||||
if (c.isLight()) c = c.darken(0.15)
|
if (c.isLight()) c = c.darken(0.15)
|
||||||
else if (c.isDark()) c = c.lighten(0.1)
|
else if (c.isDark()) c = c.lighten(0.1)
|
||||||
const to = c.darken(0.15).rotate(-5).toHex()
|
const to = c.darken(0.15).rotate(-5).toHex()
|
||||||
|
|
|
||||||
|
|
@ -226,8 +226,7 @@ const TracksList = memo(
|
||||||
track={track}
|
track={track}
|
||||||
isLiked={userLikedSongs?.ids?.includes(track.id) ?? false}
|
isLiked={userLikedSongs?.ids?.includes(track.id) ?? false}
|
||||||
isSkeleton={false}
|
isSkeleton={false}
|
||||||
isPlaying={track.id === playingTrack?.id}
|
isHighlight={track.id === playingTrack?.id}
|
||||||
subtitle={track.tns?.at(0) ?? track.alia?.at(0)}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { StrictMode } from 'react'
|
import { StrictMode } from 'react'
|
||||||
import { render } from 'react-dom'
|
import * as ReactDOMClient from 'react-dom/client'
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import * as Sentry from '@sentry/react'
|
import * as Sentry from '@sentry/react'
|
||||||
import { BrowserTracing } from '@sentry/tracing'
|
import { BrowserTracing } from '@sentry/tracing'
|
||||||
|
|
@ -20,11 +20,13 @@ Sentry.init({
|
||||||
tracesSampleRate: 1.0,
|
tracesSampleRate: 1.0,
|
||||||
})
|
})
|
||||||
|
|
||||||
render(
|
const container = document.getElementById('root') as HTMLElement
|
||||||
|
const root = ReactDOMClient.createRoot(container)
|
||||||
|
|
||||||
|
root.render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<App />
|
<App />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</StrictMode>,
|
</StrictMode>
|
||||||
document.getElementById('root')
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { RefObject } from 'react'
|
|
||||||
import { proxy, subscribe } from 'valtio'
|
import { proxy, subscribe } from 'valtio'
|
||||||
import { devtools } from 'valtio/utils'
|
import { devtools } from 'valtio/utils'
|
||||||
import { player as PlayerCore } from '@/utils/player'
|
import { player as PlayerCore } from '@/utils/player'
|
||||||
|
|
@ -33,7 +32,7 @@ export const player = proxy(PlayerCore)
|
||||||
player.init()
|
player.init()
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
window.player = player
|
;(window as any).player = player
|
||||||
}
|
}
|
||||||
|
|
||||||
// Devtools
|
// Devtools
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ export class Player {
|
||||||
*/
|
*/
|
||||||
private async _fetchTrack(trackID: TrackID) {
|
private async _fetchTrack(trackID: TrackID) {
|
||||||
const response = await fetchTracksWithReactQuery({ ids: [trackID] })
|
const response = await fetchTracksWithReactQuery({ ids: [trackID] })
|
||||||
return response?.songs?.length ? response.songs[0] : null
|
return response?.songs.length ? response.songs[0] : null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -173,8 +173,10 @@ export class Player {
|
||||||
* Play a track based on this.trackID
|
* Play a track based on this.trackID
|
||||||
*/
|
*/
|
||||||
private async _playTrack() {
|
private async _playTrack() {
|
||||||
|
const id = this.trackID
|
||||||
|
if (!id) return
|
||||||
this.state = State.LOADING
|
this.state = State.LOADING
|
||||||
const track = await this._fetchTrack(this.trackID)
|
const track = await this._fetchTrack(id)
|
||||||
if (!track) {
|
if (!track) {
|
||||||
toast('加载歌曲信息失败')
|
toast('加载歌曲信息失败')
|
||||||
return
|
return
|
||||||
|
|
@ -208,7 +210,7 @@ export class Player {
|
||||||
this.play()
|
this.play()
|
||||||
this.state = State.PLAYING
|
this.state = State.PLAYING
|
||||||
_howler.once('load', () => {
|
_howler.once('load', () => {
|
||||||
this._cacheAudio(_howler._src)
|
this._cacheAudio((_howler as any)._src)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!this._progressInterval) {
|
if (!this._progressInterval) {
|
||||||
|
|
@ -233,9 +235,6 @@ export class Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _nextFMTrack() {
|
private async _nextFMTrack() {
|
||||||
this.fmTrackList.shift()
|
|
||||||
this._playTrack()
|
|
||||||
|
|
||||||
const loadMoreTracks = async () => {
|
const loadMoreTracks = async () => {
|
||||||
if (this.fmTrackList.length <= 5) {
|
if (this.fmTrackList.length <= 5) {
|
||||||
const response = await fetchPersonalFMWithReactQuery()
|
const response = await fetchPersonalFMWithReactQuery()
|
||||||
|
|
@ -248,7 +247,11 @@ export class Player {
|
||||||
if (track?.al.picUrl) axios.get(resizeImage(track.al.picUrl, 'md'))
|
if (track?.al.picUrl) axios.get(resizeImage(track.al.picUrl, 'md'))
|
||||||
}
|
}
|
||||||
|
|
||||||
loadMoreTracks()
|
if (this.fmTrackList.length === 0) await loadMoreTracks()
|
||||||
|
this.fmTrackList.shift()
|
||||||
|
this._playTrack()
|
||||||
|
|
||||||
|
this.fmTrackList.length === 0 ? await loadMoreTracks() : loadMoreTracks()
|
||||||
prefetchNextTrack()
|
prefetchNextTrack()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -405,6 +408,7 @@ export class Player {
|
||||||
* Trash current PersonalFMTrack
|
* Trash current PersonalFMTrack
|
||||||
*/
|
*/
|
||||||
async fmTrash() {
|
async fmTrash() {
|
||||||
|
this.mode = Mode.FM
|
||||||
const trashTrackID = this.fmTrackList[0]
|
const trashTrackID = this.fmTrackList[0]
|
||||||
fmTrash(trashTrackID)
|
fmTrash(trashTrackID)
|
||||||
this._nextFMTrack()
|
this._nextFMTrack()
|
||||||
|
|
@ -424,5 +428,5 @@ export class Player {
|
||||||
export const player = new Player()
|
export const player = new Player()
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
window.howler = _howler
|
;(window as any).howler = _howler
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export default defineConfig({
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
base: './',
|
base: '/',
|
||||||
build: {
|
build: {
|
||||||
target: process.env.IS_ELECTRON ? 'esnext' : 'modules',
|
target: process.env.IS_ELECTRON ? 'esnext' : 'modules',
|
||||||
sourcemap: process.env.NODE_ENV === 'debug',
|
sourcemap: process.env.NODE_ENV === 'debug',
|
||||||
|
|
|
||||||