mirror of
https://github.com/imsyy/SPlayer.git
synced 2025-11-26 03:44:57 +08:00
Compare commits
109 Commits
v2.0.0-bet
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dc1a5b777 | ||
|
|
8a842aa6d6 | ||
|
|
d94fd4b1b3 | ||
|
|
b0a08ce1b8 | ||
|
|
77ab94a59a | ||
|
|
2b65269ba9 | ||
|
|
7ed33919ab | ||
|
|
d20512e662 | ||
|
|
9423d2bf9e | ||
|
|
54fb8e74a8 | ||
|
|
7d3d7696da | ||
|
|
e66ba52889 | ||
|
|
b146dc011e | ||
|
|
ad2422d826 | ||
|
|
6b9ba74c35 | ||
|
|
22e2653e20 | ||
|
|
4cac54c84a | ||
|
|
e5f9ecd7b5 | ||
|
|
d64cfb40ec | ||
|
|
53c30caf00 | ||
|
|
6fcce91b2b | ||
|
|
5a53dfcdf7 | ||
|
|
bbe71bd62d | ||
|
|
ecedf0b7b9 | ||
|
|
f2935f3c1c | ||
|
|
d3e677d494 | ||
|
|
e49f90b0da | ||
|
|
0359b0e470 | ||
|
|
aa2373695b | ||
|
|
cf940ff405 | ||
|
|
e7c17cf531 | ||
|
|
c6a1ca4f42 | ||
|
|
8f5008df69 | ||
|
|
428ce4be86 | ||
|
|
d46c4c4285 | ||
|
|
0b871175b2 | ||
|
|
c34c4fd880 | ||
|
|
ff00f0c283 | ||
|
|
847c2e5810 | ||
|
|
e62c81bb33 | ||
|
|
984fdb3459 | ||
|
|
019b78bf38 | ||
|
|
cf88c7669f | ||
|
|
f4383ba848 | ||
|
|
adbda459ba | ||
|
|
984d747179 | ||
|
|
c012f84064 | ||
|
|
a57a18b9f5 | ||
|
|
309c323a14 | ||
|
|
6a1e606d6d | ||
|
|
af3931847e | ||
|
|
41eadb5843 | ||
|
|
8963d719d9 | ||
|
|
0a7761ffff | ||
|
|
1a63771f2d | ||
|
|
1f9141ba33 | ||
|
|
a341a69d48 | ||
|
|
0cedfe0af3 | ||
|
|
59f492ed8f | ||
|
|
8f416ff841 | ||
|
|
99ab194e4b | ||
|
|
1e1c3e4efb | ||
|
|
43fb9b48dc | ||
|
|
c61e54d6a3 | ||
|
|
c8d195053f | ||
|
|
8cfe5d0481 | ||
|
|
fcc2f5015f | ||
|
|
9b98a45264 | ||
|
|
3c4e836fb8 | ||
|
|
a8111b9d3f | ||
|
|
8eaeffeda3 | ||
|
|
eed76966c4 | ||
|
|
b095e4eb36 | ||
|
|
3dbdf3e613 | ||
|
|
5ceca058a7 | ||
|
|
a8e867bbf9 | ||
|
|
4cb8eb0213 | ||
|
|
461f216cab | ||
|
|
2756313e4a | ||
|
|
e802a2f574 | ||
|
|
a45940b104 | ||
|
|
ac0ac5f4ea | ||
|
|
883b6d13a5 | ||
|
|
ee9bbf0687 | ||
|
|
693dc65b07 | ||
|
|
354d271582 | ||
|
|
eaaeb0f5d3 | ||
|
|
e4e8deec59 | ||
|
|
3f21704b82 | ||
|
|
7ad2bb8bde | ||
|
|
3123e4f5f8 | ||
|
|
60e43c9f40 | ||
|
|
298813a057 | ||
|
|
2db74f3a39 | ||
|
|
024ff1773e | ||
|
|
6046e5a153 | ||
|
|
3c39dbd87f | ||
|
|
c5747b6a3e | ||
|
|
750d570c3d | ||
|
|
b811b00b9f | ||
|
|
a372570038 | ||
|
|
6d5fa15098 | ||
|
|
b65369a8a6 | ||
|
|
0af0ac3cce | ||
|
|
f0ed78eed5 | ||
|
|
b1cda68c75 | ||
|
|
dd1081cfa2 | ||
|
|
046b8f3a92 | ||
|
|
72650a5419 |
12
.dockerignore
Normal file
12
.dockerignore
Normal file
@@ -0,0 +1,12 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
Dockerfile*
|
||||
docker-compose*
|
||||
.dockerignore
|
||||
.git
|
||||
.github
|
||||
.gitignore
|
||||
README.md
|
||||
LICENSE
|
||||
.vscode
|
||||
dist
|
||||
@@ -1,9 +1,18 @@
|
||||
# 根配置文件
|
||||
## 编辑器在查找配置时会停止查找更高层次的配置文件
|
||||
root = true
|
||||
|
||||
# 通配符,匹配所有文件
|
||||
[*]
|
||||
# 设置字符集为 UTF-8,确保文件中的文本使用 UTF-8 编码
|
||||
charset = utf-8
|
||||
# 使用空格作为缩进风格
|
||||
indent_style = space
|
||||
# 设置每个缩进级别的空格数量为 2
|
||||
indent_size = 2
|
||||
# 设置行尾换行符为LF(Line Feed)
|
||||
end_of_line = lf
|
||||
# 在文件的末尾插入一个新行
|
||||
insert_final_newline = true
|
||||
# 删除每一行末尾的尾随空格
|
||||
trim_trailing_whitespace = true
|
||||
@@ -21,9 +21,18 @@ RENDERER_VITE_SITE_ANTHOR = "無名"
|
||||
RENDERER_VITE_SITE_KEYWORDS = "SPlayer,云音乐,播放器,在线音乐,在线播放器,音乐播放器"
|
||||
RENDERER_VITE_SITE_DES = "一个简约的在线音乐播放器,具有音乐搜索、播放、每日推荐、私人FM、歌词显示、歌曲评论、网易云登录与云盘等功能"
|
||||
RENDERER_VITE_SITE_URL = "imsyy.top"
|
||||
RENDERER_VITE_SITE_LOGO = "/images/logo/favicon.svg"
|
||||
RENDERER_VITE_SITE_APPLE_LOGO = "/images/logo/favicon-apple.png"
|
||||
|
||||
# Cookie
|
||||
## 咪咕音乐 Cookie
|
||||
MAIN_VITE_MIGU_COOKIE = ""
|
||||
|
||||
# 公告配置
|
||||
## 若无需公告,请将标题或内容任意一项设为空即可
|
||||
## 公告类型
|
||||
RENDERER_VITE_ANN_TYPE = "info"
|
||||
## 公告标题
|
||||
RENDERER_VITE_ANN_TITLE = ""
|
||||
## 公告内容
|
||||
RENDERER_VITE_ANN_CONTENT = ""
|
||||
## 公告时长(毫秒)不可超过 999999
|
||||
RENDERER_VITE_ANN_DURATION = 8000
|
||||
@@ -2,3 +2,5 @@ node_modules
|
||||
dist
|
||||
out
|
||||
.gitignore
|
||||
auto-imports.d.ts
|
||||
components.d.ts
|
||||
|
||||
@@ -49,5 +49,8 @@ module.exports = {
|
||||
$notification: true,
|
||||
$changeThemeColor: true,
|
||||
$canNotConnect: true,
|
||||
$refreshCloudCatch: true,
|
||||
$cleanAll: true,
|
||||
$player: true,
|
||||
},
|
||||
};
|
||||
|
||||
17
.github/ISSUE_TEMPLATE/add.yml
vendored
17
.github/ISSUE_TEMPLATE/add.yml
vendored
@@ -1,17 +0,0 @@
|
||||
name: 添加功能
|
||||
description: 请填写希望添加的功能的具体信息
|
||||
title: "添加功能"
|
||||
labels: [add]
|
||||
body:
|
||||
- type: input
|
||||
id: name
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "希望添加什么功能?"
|
||||
placeholder: "请填写功能名称"
|
||||
- type: textarea
|
||||
id: other
|
||||
attributes:
|
||||
label: "具体信息"
|
||||
description: "请详细描述希望添加的功能的具体信息"
|
||||
4
.github/ISSUE_TEMPLATE/bug.yml
vendored
4
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -1,5 +1,6 @@
|
||||
name: 遇到问题
|
||||
description: 关于使用过程中遇到的问题
|
||||
title: 请填写标题
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: input
|
||||
@@ -30,4 +31,5 @@ body:
|
||||
id: other
|
||||
attributes:
|
||||
label: "具体信息"
|
||||
description: "有需要补充的信息吗?比如控制台的报错什么的"
|
||||
description: "请填写完整的复现步骤和遇到的问题,包括但不限于报错信息、控制台输出、网络请求等"
|
||||
placeholder: "请填写具体的复现步骤和遇到的问题"
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 添加功能
|
||||
url: https://github.com/imsyy/SPlayer/discussions/new?category=%E6%83%B3%E6%B3%95-ideas
|
||||
about: 新的功能建议和提问答疑请到讨论区发起
|
||||
- name: 转到讨论区
|
||||
url: https://github.com/imsyy/SPlayer/discussions
|
||||
about: Issues 用于反馈 Bug, 新的功能建议和提问答疑请到讨论区发起
|
||||
- name: 提问的艺术
|
||||
url: https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md
|
||||
about: 默认所有 Issues 发起者均已了解此处的内容
|
||||
29
.github/workflows/build.yml
vendored
29
.github/workflows/build.yml
vendored
@@ -15,12 +15,20 @@ jobs:
|
||||
steps:
|
||||
# 检出 Git 仓库
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4
|
||||
# 安装 Node.js
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18.x"
|
||||
node-version: "20.x"
|
||||
# 复制环境变量文件
|
||||
- name: Copy .env.example
|
||||
run: |
|
||||
if (-not (Test-Path .env)) {
|
||||
Copy-Item .env.example .env
|
||||
} else {
|
||||
Write-Host ".env file already exists. Skipping the copy step."
|
||||
}
|
||||
# 安装项目依赖
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
@@ -35,7 +43,20 @@ jobs:
|
||||
npx rimraf "dist/!(*.exe)"
|
||||
# 上传构建产物
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3.1.3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: SPlayer-dev
|
||||
path: dist
|
||||
# 创建 GitHub Release
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
name: ${{ github.ref }}-rc
|
||||
body: This version is still under development, currently only provides windows version, non-developers please do not use!
|
||||
draft: false
|
||||
prerelease: true
|
||||
files: dist/*.exe
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
|
||||
46
.github/workflows/docker.yml
vendored
Normal file
46
.github/workflows/docker.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Publish Docker image
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to multiple registries
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
imsyy/splayer
|
||||
ghcr.io/${{ github.repository }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
83
.github/workflows/release.yml
vendored
83
.github/workflows/release.yml
vendored
@@ -12,15 +12,24 @@ jobs:
|
||||
build-windows:
|
||||
name: Build for Windows
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
# 检出 Git 仓库
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4
|
||||
# 安装 Node.js
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18.x"
|
||||
node-version: "20.x"
|
||||
# 复制环境变量文件
|
||||
- name: Copy .env.example
|
||||
run: |
|
||||
if (-not (Test-Path .env)) {
|
||||
Copy-Item .env.example .env
|
||||
} else {
|
||||
Write-Host ".env file already exists. Skipping the copy step."
|
||||
}
|
||||
# 安装项目依赖
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
@@ -32,37 +41,42 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
# 上传构建产物
|
||||
- name: Upload Windows artifact
|
||||
uses: actions/upload-artifact@v3.1.3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: SPlarer-Win
|
||||
if-no-files-found: ignore
|
||||
path: |
|
||||
dist/*.exe
|
||||
dist/*.msi
|
||||
path: dist/*.*
|
||||
# 创建 GitHub Release
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v0.1.15
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
draft: true
|
||||
files: |
|
||||
dist/*.exe
|
||||
dist/*.msi
|
||||
files: dist/*.*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
# Mac
|
||||
build-macos:
|
||||
name: Build for macOS
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
# 检出 Git 仓库
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4
|
||||
# 安装 Node.js
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18.x"
|
||||
node-version: "20.x"
|
||||
# 复制环境变量文件
|
||||
- name: Copy .env.example
|
||||
run: |
|
||||
if [ ! -f .env ]; then
|
||||
cp .env.example .env
|
||||
else
|
||||
echo ".env file already exists. Skipping the copy step."
|
||||
fi
|
||||
# 安装项目依赖
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
@@ -74,40 +88,45 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
# 上传构建产物
|
||||
- name: Upload macOS artifact
|
||||
uses: actions/upload-artifact@v3.1.3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: SPlarer-Macos
|
||||
if-no-files-found: ignore
|
||||
path: |
|
||||
dist/*.dmg
|
||||
dist/*.zip
|
||||
path: dist/*.*
|
||||
# 创建 GitHub Release
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v0.1.15
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
draft: true
|
||||
files: |
|
||||
dist/*.dmg
|
||||
dist/*.zip
|
||||
files: dist/*.*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
# Linux
|
||||
build-linux:
|
||||
name: Build for Linux
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
# 检出 Git 仓库
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4
|
||||
# 安装 Node.js
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18.x"
|
||||
node-version: "20.x"
|
||||
# 更新 Ubuntu 软件源
|
||||
- name: Ubuntu Update with sudo
|
||||
run: sudo apt-get update
|
||||
# 复制环境变量文件
|
||||
- name: Copy .env.example
|
||||
run: |
|
||||
if [ ! -f .env ]; then
|
||||
cp .env.example .env
|
||||
else
|
||||
echo ".env file already exists. Skipping the copy step."
|
||||
fi
|
||||
# 安装项目依赖
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
@@ -119,23 +138,17 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
# 上传构建产物
|
||||
- name: Upload Linux artifact
|
||||
uses: actions/upload-artifact@v3.1.3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: SPlarer-Linux
|
||||
if-no-files-found: ignore
|
||||
path: |
|
||||
dist/*.AppImage
|
||||
dist/*.deb
|
||||
dist/*.rpm
|
||||
path: dist/*.*
|
||||
# 创建 GitHub Release
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v0.1.15
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
draft: true
|
||||
files: |
|
||||
dist/*.AppImage
|
||||
dist/*.deb
|
||||
dist/*.rpm
|
||||
files: dist/*.*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -14,9 +14,7 @@ dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
out
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
.env
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
|
||||
5
.npmrc
5
.npmrc
@@ -1,2 +1,5 @@
|
||||
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
|
||||
registry=https://registry.npmmirror.com
|
||||
disturl=https://registry.npmmirror.com/-/binary/node
|
||||
# ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
|
||||
ELECTRON_MIRROR=https://registry.npmmirror.com/-/binary/electron/
|
||||
shamefully-hoist=true
|
||||
|
||||
@@ -4,3 +4,5 @@ pnpm-lock.yaml
|
||||
LICENSE.md
|
||||
tsconfig.json
|
||||
tsconfig.*.json
|
||||
auto-imports.d.ts
|
||||
components.d.ts
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
# 是否使用单引号而不是双引号
|
||||
singleQuote: false
|
||||
# 是否在语句末尾使用分号
|
||||
semi: true
|
||||
# 每行的最大打印宽度
|
||||
printWidth: 100
|
||||
# 是否在对象和数组的末尾加上逗号
|
||||
trailingComma: all
|
||||
|
||||
29
Dockerfile
Normal file
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
||||
# build
|
||||
FROM node:18-alpine as builder
|
||||
|
||||
RUN apk update && apk add --no-cache git
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN [ ! -e ".env" ] && cp .env.example .env || true
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# nginx
|
||||
FROM nginx:1.25.3-alpine-slim as app
|
||||
|
||||
COPY --from=builder /app/out/renderer /usr/share/nginx/html
|
||||
|
||||
COPY --from=builder /app/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
RUN apk add --no-cache npm
|
||||
|
||||
RUN npm install -g NeteaseCloudMusicApi
|
||||
|
||||
CMD nginx && npx NeteaseCloudMusicApi
|
||||
149
LICENSE
149
LICENSE
@@ -1,23 +1,21 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
@@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
@@ -72,7 +60,7 @@ modification follow.
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
@@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
@@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
427
README.md
427
README.md
@@ -1,25 +1,28 @@
|
||||
> [!IMPORTANT]
|
||||
> ## 🎉 当前项目正在重构中 🎉
|
||||
>
|
||||
> - 目前版本进入维护模式,仅在遇到重大问题时会进行修复
|
||||
> - 支持客户端与网页端
|
||||
> - 支持现有版本所有功能
|
||||
> - 新增支持播放与管理本地歌曲
|
||||
# SPlayer
|
||||
|
||||
<div align="center">
|
||||
<img alt="logo" height="80" src="./public/images/logo/favicon.png" />
|
||||
<h2>SPlayer</h2>
|
||||
<p>一个简约的音乐播放器</p>
|
||||
<img alt="main" src="./screenshots/main.png" />
|
||||
</div>
|
||||
<br />
|
||||
> 一个简约的音乐播放器
|
||||
|
||||

|
||||
|
||||
## 说明
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> ### 严肃警告
|
||||
>
|
||||
> - 请务必遵守 [GNU Affero General Public License (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.html) 许可协议
|
||||
> - 在您的修改、演绎、分发或派生项目中,必须同样采用 **AGPL-3.0** 许可协议,**并在适当的位置包含本项目的许可和版权信息**
|
||||
> - **禁止用于售卖或其他商业用途**,如若发现,作者保留追究法律责任的权利
|
||||
> - 若发现未遵守 **AGPL-3.0** 许可协议的行为,**本项目将永久停更**
|
||||
> - 感谢您的尊重与理解
|
||||
|
||||
- 本项目采用 [Vue 3](https://cn.vuejs.org/) 全家桶和 [Naïve UI](https://www.naiveui.com/) 组件库及 [Electron](https://www.electronjs.org/zh/docs/latest/) 开发
|
||||
- 支持网页端与客户端,由于设备有限,目前仅适配 `Win`,其他平台可自行构建
|
||||
- ~~仅对移动端做了基础适配,**不保证功能全部可用**~~
|
||||
- 欢迎各位大佬指点和 `Star` 哦 😍
|
||||
- 支持网页端与客户端,由于设备有限,目前仅适配 `Win`,其他平台可自行解决兼容性后进行构建
|
||||
- 仅对移动端做了基础适配,**不保证功能全部可用**
|
||||
|
||||
> 请注意,本程序不打算开发移动端,也不会对移动端进行完美适配,仅保证基础可用性
|
||||
|
||||
- 欢迎各位大佬 `Star` 😍
|
||||
|
||||
## 👀 Demo
|
||||
|
||||
@@ -27,40 +30,34 @@
|
||||
|
||||
## 🎉 功能
|
||||
|
||||
- 支持扫码登录
|
||||
- 支持手机号登录
|
||||
- 自动进行每日签到及云贝签到
|
||||
- 封面主题色自适应
|
||||
- 本地歌曲管理及分类 ~~以及音乐标签编辑~~
|
||||
- **支持播放部分无版权歌曲(可能会与原曲不匹配,客户端独占功能)**
|
||||
- 下载歌曲(最高支持 Hi-Res)
|
||||
- 新建歌单及歌单编辑
|
||||
- 收藏 / 取消收藏歌单或歌手
|
||||
- 每日推荐歌曲
|
||||
- 私人 FM
|
||||
- 云盘音乐上传
|
||||
- 云盘内歌曲播放
|
||||
- 云盘内歌曲纠正
|
||||
- 云盘歌曲删除
|
||||
- 支持逐字歌词
|
||||
- 歌词滚动以及歌词翻译
|
||||
- MV 与视频播放
|
||||
- 音乐频谱显示( 暂时去除,还待完善 )
|
||||
- 音乐渐入渐出
|
||||
- 支持 PWA
|
||||
- 支持评论区及评论点赞
|
||||
- 明暗模式自动 / 手动切换
|
||||
- ~~移动端基础适配~~
|
||||
- ~~`i18n` 支持~~
|
||||
- ✨ 支持扫码登录
|
||||
- 📱 支持手机号登录
|
||||
- 📅 自动进行每日签到及云贝签到
|
||||
- 🎨 封面主题色自适应
|
||||
- 🌚 Light / Dark 模式自动切换
|
||||
- 📁 本地歌曲管理及分类(建议先使用 [音乐标签](https://www.cnblogs.com/vinlxc/p/11347744.html) 进行匹配后再使用)
|
||||
- 🎵 **支持播放部分无版权歌曲(可能会与原曲不匹配,客户端独占功能)**
|
||||
- ⬇️ 下载歌曲(最高支持 Hi-Res)
|
||||
- ➕ 新建歌单及歌单编辑
|
||||
- ❤️ 收藏 / 取消收藏歌单或歌手
|
||||
- 🎶 每日推荐歌曲
|
||||
- 📻 私人 FM
|
||||
- ☁️ 云盘音乐上传
|
||||
- 📂 云盘内歌曲播放
|
||||
- 🔄 云盘内歌曲纠正
|
||||
- 🗑️ 云盘歌曲删除
|
||||
- 📝 支持逐字歌词
|
||||
- 🔄 歌词滚动以及歌词翻译
|
||||
- 📹 MV 与视频播放
|
||||
- 🎶 音乐频谱显示
|
||||
- ⏭️ 音乐渐入渐出
|
||||
- 🔄 支持 PWA
|
||||
- 💬 支持评论区及评论点赞
|
||||
- 🌓 明暗模式自动 / 手动切换
|
||||
- 📱 移动端基础适配
|
||||
- ~~🌐 `i18n` 支持~~
|
||||
|
||||
#### 待办
|
||||
|
||||
- [ ] 完善音乐频谱
|
||||
- [ ] 添加桌面歌词
|
||||
- [ ] 多种布局方式
|
||||
- [ ] 发表评论
|
||||
|
||||
## 🖼️ Screenshots
|
||||
## 🖼️ screenshots
|
||||
|
||||
> 开发中,仅供参考
|
||||
|
||||
@@ -118,68 +115,109 @@
|
||||
|
||||
[Dev Workflow](https://github.com/imsyy/SPlayer/actions/workflows/build.yml)
|
||||
|
||||
## ⚙️ 部署
|
||||
## ⚙️ Docker 部署
|
||||
|
||||
> Vercel 等托管平台可在 Fork 后一键导入并自动部署
|
||||
> 安装及配置 `Docker` 将不在此处说明,请自行解决
|
||||
|
||||
### API 服务(客户端无需理会,如果需要网页端,则必需部署)
|
||||
### 本地构建
|
||||
|
||||
> 本程序依赖 [NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi) 运行,请确保您已成功部署该项目
|
||||
|
||||
- 请在根目录下的 `.env` 文件中的 `RENDERER_VITE_SERVER_URL` 中填入 API 地址(必需)
|
||||
|
||||
```js
|
||||
RENDERER_VITE_SERVER_URL = "your api url";
|
||||
```
|
||||
|
||||
### 安装依赖
|
||||
> 请尽量拉取最新分支后使用本地构建方式,在线部署的仓库可能更新不及时
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
# 或者
|
||||
yarn install
|
||||
# 或者
|
||||
npm install
|
||||
# 构建
|
||||
docker build -t splayer .
|
||||
|
||||
# 运行
|
||||
docker run -d --name SPlayer -p 7899:7899 splayer
|
||||
# 或使用 Docker Compose
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### 开发
|
||||
### 在线部署
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
# 或者
|
||||
yarn dev
|
||||
# 或者
|
||||
npm dev
|
||||
# 从 Docker Hub 拉取
|
||||
docker pull imsyy/splayer:latest
|
||||
# 从 GitHub ghcr 拉取
|
||||
docker pull ghcr.io/imsyy/splayer:latest
|
||||
|
||||
# 运行
|
||||
docker run -d --name SPlayer -p 7899:7899 imsyy/splayer:latest
|
||||
```
|
||||
|
||||
### 构建网页端
|
||||
以上步骤成功后,将会在本地 [localhost:7899](http://localhost:7899/) 启动,如需更换端口,请自行修改命令行中的端口号
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
# 或者
|
||||
yarn build
|
||||
# 或者
|
||||
npm build
|
||||
```
|
||||
## ⚙️ Vercel 部署
|
||||
|
||||
构建完成后可将生成的 `out/renderer` 文件夹内的文件上传至服务器
|
||||
> 其他部署平台大致相同,在此不做说明
|
||||
|
||||
若使用的为第三方部署平台,比如 `Vercel`,请将 `Build and Output Settings` 中的 `Output Directory` 改为 `out/renderer`
|
||||
1. 本程序依赖 [NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi) 运行,请确保您已成功部署该项目,并成功取得在线访问地址
|
||||
2. 点击本仓库右上角的 `Fork`,复制本仓库到你的 `GitHub` 账号
|
||||
3. 复制 `/.env.example` 文件并重命名为 `/.env`
|
||||
4. 将 `.env` 文件中的 `RENDERER_VITE_SERVER_URL` 改为第一步得到的 API 地址
|
||||
|
||||

|
||||
```js
|
||||
RENDERER_VITE_SERVER_URL = "https://example.com";
|
||||
```
|
||||
|
||||
### 构建客户端
|
||||
5. 将 `Build and Output Settings` 中的 `Output Directory` 改为 `out/renderer`
|
||||
|
||||
```bash
|
||||
# win
|
||||
pnpm build:win
|
||||
# linux
|
||||
pnpm build:linux
|
||||
# mac
|
||||
pnpm build:mac
|
||||
```
|
||||

|
||||
|
||||
构建完成后可在 `dist` 文件夹中打开可执行文件来完成安装操作
|
||||
6. 点击 `Deploy`,即可成功部署
|
||||
|
||||
## ⚙️ 服务器部署
|
||||
|
||||
1. 重复 `⚙️ Vercel 部署` 中的 1 - 4 步骤
|
||||
2. 克隆仓库
|
||||
|
||||
> 将链接中的 example/repository.git 替换为你要克隆的实际仓库的地址
|
||||
|
||||
```bash
|
||||
git clone https://github.com/example/repository.git
|
||||
```
|
||||
|
||||
3. 安装依赖
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
# 或者
|
||||
yarn install
|
||||
# 或者
|
||||
npm install
|
||||
```
|
||||
|
||||
4. 编译打包
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
# 或者
|
||||
yarn build
|
||||
# 或者
|
||||
npm build
|
||||
```
|
||||
|
||||
5. 将站点运行目录设置为 `out/renderer` 目录
|
||||
|
||||
## ⚙️ 本地部署
|
||||
|
||||
1. 本地部署需要用到 `Node.js`。可前往 [Node.js 官网](https://nodejs.org/zh-cn/) 下载安装包,请下载最新稳定版
|
||||
2. 安装 pnpm
|
||||
|
||||
```bash
|
||||
npm install pnpm -g
|
||||
```
|
||||
|
||||
3. 克隆仓库并拉取至本地,此处不再赘述
|
||||
4. 使用 `pnpm install` 安装项目依赖(若安装过程中遇到网络错误,请使用国内镜像源替代,此处不再赘述)
|
||||
5. 复制 `/.env.example` 文件并重命名为 `/.env` 并修改配置
|
||||
6. 打包客户端,请依据你的系统类型来选择,打包成功后,会输出安装包或可执行文件在 `/dist` 目录中,可自行安装
|
||||
|
||||
| 命令 | 系统类型 |
|
||||
| ------------------ | -------- |
|
||||
| `pnpm build:win` | Windows |
|
||||
| `pnpm build:linux` | Linux |
|
||||
| `pnpm build:mac` | MacOS |
|
||||
|
||||
## 😘 鸣谢
|
||||
|
||||
@@ -188,18 +226,9 @@ pnpm build:mac
|
||||
- [NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)
|
||||
- [YesPlayMusic](https://github.com/qier222/YesPlayMusic)
|
||||
- [UnblockNeteaseMusic](https://github.com/UnblockNeteaseMusic/server)
|
||||
- [BlurLyric](https://github.com/Project-And-Factory/BlurLyric)
|
||||
- [Vue-mmPlayer](https://github.com/maomao1996/Vue-mmPlayer)
|
||||
|
||||
## 📜 开源许可
|
||||
|
||||
- **本项目仅供个人学习研究使用,禁止用于商业及非法用途**
|
||||
- 本项目基于 [GNU General Public License version 3](https://opensource.org/license/gpl-3-0/) 许可进行开源
|
||||
1. **修改和分发:** 任何对本项目的修改和分发都必须基于 GPL Version 3 进行,源代码必须一并提供
|
||||
2. **派生作品:** 任何派生作品必须同样采用 GPL Version 3,并在适当的地方注明原始项目的许可证
|
||||
3. **免责声明:** 根据 GPL Version 3,本项目不提供任何明示或暗示的担保。请详细阅读 GPL Version 3以了解完整的免责声明内容
|
||||
4. **社区参与:** 欢迎社区的参与和贡献,我们鼓励开发者一同改进和维护本项目
|
||||
5. **许可证链接:** 请阅读 [GNU General Public License version 3](https://opensource.org/license/gpl-3-0/) 了解更多详情
|
||||
- [refined-now-playing-netease](https://github.com/solstice23/refined-now-playing-netease)
|
||||
- [material-color-utilities](https://github.com/material-foundation/material-color-utilities)
|
||||
|
||||
## 📢 免责声明
|
||||
|
||||
@@ -210,3 +239,191 @@ pnpm build:mac
|
||||
请使用者在使用本项目时遵守相关法律法规,**不要将本项目用于任何商业及非法用途。如有违反,一切后果由使用者自负。** 同时,使用者应该自行承担因使用本项目而带来的风险和责任。本项目开发者不对本项目所提供的服务和内容做出任何保证
|
||||
|
||||
感谢您的理解
|
||||
|
||||
## 📜 开源许可
|
||||
|
||||
- **本项目仅供个人学习研究使用,禁止用于商业及非法用途**
|
||||
- 本项目基于 [GNU Affero General Public License (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.html) 许可进行开源
|
||||
1. **修改和分发:** 任何对本项目的修改和分发都必须基于 AGPL-3.0 进行,源代码必须一并提供
|
||||
2. **派生作品:** 任何派生作品必须同样采用 AGPL-3.0,并在适当的地方注明原始项目的许可证
|
||||
3. **注明原作者:** 在任何修改、派生作品或其他分发中,必须在适当的位置明确注明原作者及其贡献
|
||||
4. **免责声明:** 根据 AGPL-3.0,本项目不提供任何明示或暗示的担保。请详细阅读 [GNU Affero General Public License (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.html) 以了解完整的免责声明内容
|
||||
5. **社区参与:** 欢迎社区的参与和贡献,我们鼓励开发者一同改进和维护本项目
|
||||
6. **许可证链接:** 请阅读 [GNU Affero General Public License (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.html) 了解更多详情
|
||||
|
||||
## 📂 目录结构
|
||||
|
||||
<details>
|
||||
<summary>查看目录结构详情</summary>
|
||||
|
||||
> ChatGPT 写的,如有错误,请见谅
|
||||
|
||||
```dir
|
||||
├── auto-imports.d.ts # 自动导入
|
||||
├── components.d.ts # 自动导入
|
||||
├── docker-compose.yml # Docker Compose
|
||||
├── Dockerfile # Docker
|
||||
├── electron # Electron
|
||||
│ ├── main # Electron 主进程
|
||||
│ │ ├── index.js # 主进程入口
|
||||
│ │ ├── mainIpcMain.js # 主进程与渲染进程通信
|
||||
│ │ ├── startMainServer.js # 启动主进程服务器
|
||||
│ │ ├── startNcmServer.js # 启动网易云音乐服务
|
||||
│ │ └── utils # 主进程工具函数
|
||||
│ │ ├── checkUpdates.js # 检查更新
|
||||
│ │ ├── createGlobalShortcut.js # 创建全局快捷键
|
||||
│ │ ├── createSystemTray.js # 创建系统托盘
|
||||
│ │ ├── getNeteaseMusicUrl.js # 解灰
|
||||
│ │ ├── kwDES.js # DES加密算法
|
||||
│ │ └── readDirAsync.js # 异步读取目录
|
||||
│ └── preload # Electron 预加载脚本
|
||||
│ └── index.js # 预加载脚本入口文件
|
||||
├── electron-builder.yml # Electron Builder
|
||||
├── electron.vite.config.js # Electron Vite
|
||||
├── index.html # 主页面 HTML
|
||||
├── LICENSE # 项目许可证
|
||||
├── nginx.conf # Nginx 配置
|
||||
├── src # 项目源代码
|
||||
│ ├── api # API 相关
|
||||
│ │ ├── ./..
|
||||
│ ├── App.vue # 根组件
|
||||
│ ├── assets # 静态资源
|
||||
│ │ ├── emoji.json # 表情数据
|
||||
│ │ ├── icon.json # 图标数据
|
||||
│ │ └── themeColor.json # 主题颜色数据
|
||||
│ ├── components # 组件目录
|
||||
│ │ ├── Cover # 封面相关组件目录
|
||||
│ │ │ ├── CoverDropdown.vue # 封面下拉组件
|
||||
│ │ │ ├── MainCover.vue # 主封面组件
|
||||
│ │ │ ├── SpecialCoverCard.vue # 特殊封面卡片组件
|
||||
│ │ │ └── SpecialCover.vue # 特殊封面组件
|
||||
│ │ ├── Global # 全局组件目录
|
||||
│ │ │ ├── MainLayout.vue # 主布局组件
|
||||
│ │ │ ├── Menu.vue # 菜单组件
|
||||
│ │ │ ├── Pagination.vue # 分页组件
|
||||
│ │ │ ├── Playlist.vue # 歌单组件
|
||||
│ │ │ ├── Provider.vue # 全局化配置组件
|
||||
│ │ │ └── SvgIcon.vue # SVG 图标组件
|
||||
│ │ ├── List # 列表组件目录
|
||||
│ │ │ ├── CommentList.vue # 评论列表组件
|
||||
│ │ │ ├── SongListDropdown.vue # 歌曲下拉组件
|
||||
│ │ │ └── SongList.vue # 歌曲列表组件
|
||||
│ │ ├── Modal # 弹窗相关组件目录
|
||||
│ │ │ ├── AddPlaylist.vue # 添加歌单组件
|
||||
│ │ │ ├── CloudSongMatch.vue # 云盘歌曲匹配组件
|
||||
│ │ │ ├── CreatePlaylist.vue # 创建歌单组件
|
||||
│ │ │ ├── DownloadSong.vue # 下载歌曲组件
|
||||
│ │ │ ├── LoginPhone.vue # 手机登录组件
|
||||
│ │ │ ├── LoginQRCode.vue # 二维码登录组件
|
||||
│ │ │ ├── Login.vue # 登录组件
|
||||
│ │ │ ├── PlaylistUpdate.vue # 歌单编辑组件
|
||||
│ │ │ └── UpCloudSong.vue # 上传云盘歌曲组件
|
||||
│ │ ├── Nav # 导航相关组件目录
|
||||
│ │ │ ├── MainNav.vue # 主导航组件
|
||||
│ │ │ └── UserData.vue # 用户数据组件
|
||||
│ │ ├── Player # 播放器相关组件目录
|
||||
│ │ │ ├── CountDown.vue # 倒计时组件
|
||||
│ │ │ ├── FullPlayer.vue # 全屏播放器组件
|
||||
│ │ │ ├── Lyric.vue # 歌词组件
|
||||
│ │ │ ├── MainControl.vue # 主控制组件
|
||||
│ │ │ ├── PlayerControl.vue # 播放器控制组件
|
||||
│ │ │ ├── PlayerCover.vue # 播放器封面组件
|
||||
│ │ │ └── PrivateFm.vue # 私人 FM 组件
|
||||
│ │ ├── Search # 搜索相关组件
|
||||
│ │ │ ├── SearchHot.vue # 热门搜索组件
|
||||
│ │ │ ├── SearchInp.vue # 搜索输入组件
|
||||
│ │ │ └── SearchSuggestions.vue # 搜索建议组件
|
||||
│ │ └── WinDom # 窗口 DOM 相关组件
|
||||
│ │ └── TitleBar.vue # 标题栏组件
|
||||
│ ├── main.js # Vue 应用的入口文件
|
||||
│ ├── router # Vue Router 相关文件夹
|
||||
│ │ ├── index.js # Vue Router 入口文件
|
||||
│ │ └── routes.js # 路由配置文件
|
||||
│ ├── stores # Vuex Store 相关文件夹
|
||||
│ │ ├── indexedDB.js # IndexedDB 数据库相关文件
|
||||
│ │ ├── index.js # Vuex Store 入口文件
|
||||
│ │ ├── musicData.js # 音乐数据相关文件
|
||||
│ │ ├── siteData.js # 网站数据相关文件
|
||||
│ │ ├── siteSettings.js # 网站设置相关文件
|
||||
│ │ └── siteStatus.js # 网站状态相关文件
|
||||
│ ├── style # 样式相关文件夹
|
||||
│ │ ├── animate.scss # 动画样式文件
|
||||
│ │ └── main.scss # 主样式文件
|
||||
│ ├── utils # 工具函数文件夹
|
||||
│ │ ├── auth.js # 认证相关函数
|
||||
│ │ ├── base64.js # Base64编码解码相关函数
|
||||
│ │ ├── color-utils.js # 颜色工具函数
|
||||
│ │ ├── cover-color.js # 封面颜色相关函数
|
||||
│ │ ├── debounce.js # 防抖函数
|
||||
│ │ ├── formatData.js # 数据格式化函数
|
||||
│ │ ├── formRules.js # 表单验证规则
|
||||
│ │ ├── globalEvents.js # 全局事件处理函数
|
||||
│ │ ├── globalShortcut.js # 全局快捷键相关函数
|
||||
│ │ ├── helper.js # 辅助函数
|
||||
│ │ ├── parseLyric.js # 解析歌词函数
|
||||
│ │ ├── Player.js # 播放器控制相关函数
|
||||
│ │ ├── request.js # 网络请求相关函数
|
||||
│ │ ├── throttle.js # 节流函数
|
||||
│ │ ├── timeTools.js # 时间工具函数
|
||||
│ │ └── userSignIn.js # 用户登录相关函数
|
||||
│ └── views # Vue组件文件夹
|
||||
│ ├── Artist # 艺术家相关组件
|
||||
│ │ ├── albums.vue # 艺术家专辑组件
|
||||
│ │ ├── hot.vue # 艺术家热门组件
|
||||
│ │ ├── index.vue # 艺术家主组件
|
||||
│ │ ├── songs.vue # 艺术家歌曲组件
|
||||
│ │ └── videos.vue # 艺术家视频组件
|
||||
│ ├── Cloud.vue # 云盘组件
|
||||
│ ├── Comment.vue # 评论组件
|
||||
│ ├── DailySongs.vue # 每日推荐组件
|
||||
│ ├── Discover # 发现音乐相关组件
|
||||
│ │ ├── artists.vue # 发现音乐艺术家组件
|
||||
│ │ ├── index.vue # 发现音乐主组件
|
||||
│ │ ├── new.vue # 发现音乐新歌组件
|
||||
│ │ ├── playlists.vue # 发现音乐歌单组件
|
||||
│ │ └── toplists.vue # 发现音乐排行榜组件
|
||||
│ ├── History.vue # 历史记录组件
|
||||
│ ├── Home.vue # 主页组件
|
||||
│ ├── Like # 我喜欢的相关组件
|
||||
│ │ ├── albums.vue # 我喜欢的专辑组件
|
||||
│ │ ├── artists.vue # 我喜欢的艺术家组件
|
||||
│ │ ├── index.vue # 我喜欢的主组件
|
||||
│ │ ├── playlists.vue # 我喜欢的歌单组件
|
||||
│ │ └── videos.vue # 我喜欢的视频组件
|
||||
│ ├── List # 列表相关组件
|
||||
│ │ ├── album.vue # 专辑组件
|
||||
│ │ └── playlist.vue # 歌单组件
|
||||
│ │ └── dj.vue # 电台组件
|
||||
│ ├── Local # 本地音乐相关组件
|
||||
│ │ ├── albums.vue # 本地音乐专辑组件
|
||||
│ │ ├── artists.vue # 本地音乐艺术家组件
|
||||
│ │ ├── index.vue # 本地音乐主组件
|
||||
│ │ └── songs.vue # 本地音乐歌曲组件
|
||||
│ ├── Player.vue # 视频播放器组件
|
||||
│ ├── Dj # 电台相关组件
|
||||
│ │ └── index.vue # 电台主组件
|
||||
│ │ └── type.vue # 电台分类组件
|
||||
│ ├── Search # 搜索相关组件
|
||||
│ │ ├── albums.vue # 搜索专辑组件
|
||||
│ │ ├── artists.vue # 搜索艺术家组件
|
||||
│ │ ├── index.vue # 搜索主组件
|
||||
│ │ ├── playlists.vue # 搜索歌单组件
|
||||
│ │ ├── songs.vue # 搜索歌曲组件
|
||||
│ │ └── videos.vue # 搜索视频组件
|
||||
│ │ └── djs.vue # 搜索电台组件
|
||||
│ ├── Setting # 设置相关组件
|
||||
│ │ └── index.vue # 设置主组件
|
||||
│ ├── Song.vue
|
||||
│ ├── State
|
||||
│ │ ├── 403.vue
|
||||
│ │ ├── 404.vue
|
||||
│ │ └── 500.vue
|
||||
│ └── Test.vue
|
||||
└── vercel.json # Vercel 部署配置
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## ⭐ Star History
|
||||
|
||||
[](https://star-history.com/#imsyy/SPlayer&Date)
|
||||
|
||||
3
auto-imports.d.ts
vendored
3
auto-imports.d.ts
vendored
@@ -65,5 +65,6 @@ declare global {
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
|
||||
16
components.d.ts
vendored
16
components.d.ts
vendored
@@ -8,27 +8,31 @@ export {}
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AddPlaylist: typeof import('./src/components/Modal/AddPlaylist.vue')['default']
|
||||
CloudSongMatch: typeof import('./src/components/Modal/CloudSongMatch.vue')['default']
|
||||
CommentList: typeof import('./src/components/List/CommentList.vue')['default']
|
||||
CountDown: typeof import('./src/components/Player/CountDown.vue')['default']
|
||||
CoverDropdown: typeof import('./src/components/Cover/CoverDropdown.vue')['default']
|
||||
CoverPlayBtn: typeof import('./src/components/Cover/CoverPlayBtn.vue')['default']
|
||||
CreatePlaylist: typeof import('./src/components/Modal/CreatePlaylist.vue')['default']
|
||||
DownloadSong: typeof import('./src/components/Modal/DownloadSong.vue')['default']
|
||||
FullPlayer: typeof import('./src/components/Player/FullPlayer.vue')['default']
|
||||
Login: typeof import('./src/components/Modal/Login.vue')['default']
|
||||
LoginPhone: typeof import('./src/components/Modal/LoginPhone.vue')['default']
|
||||
LoginQRCode: typeof import('./src/components/Modal/LoginQRCode.vue')['default']
|
||||
Lyric: typeof import('./src/components/Player/Lyric.vue')['default']
|
||||
MainControl: typeof import('./src/components/Player/MainControl.vue')['default']
|
||||
MainCover: typeof import('./src/components/Cover/MainCover.vue')['default']
|
||||
MainLayout: typeof import('./src/components/Global/MainLayout.vue')['default']
|
||||
MainNav: typeof import('./src/components/Nav/MainNav.vue')['default']
|
||||
Menu: typeof import('./src/components/Global/Menu.vue')['default']
|
||||
NAlert: typeof import('naive-ui')['NAlert']
|
||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||
NBackTop: typeof import('naive-ui')['NBackTop']
|
||||
NBadge: typeof import('naive-ui')['NBadge']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
NCard: typeof import('naive-ui')['NCard']
|
||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDataTable: typeof import('naive-ui')['NDataTable']
|
||||
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
||||
NDivider: typeof import('naive-ui')['NDivider']
|
||||
NDrawer: typeof import('naive-ui')['NDrawer']
|
||||
@@ -36,6 +40,7 @@ declare module 'vue' {
|
||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||
NEmpty: typeof import('naive-ui')['NEmpty']
|
||||
NFlex: typeof import('naive-ui')['NFlex']
|
||||
NForm: typeof import('naive-ui')['NForm']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
NGi: typeof import('naive-ui')['NGi']
|
||||
@@ -48,7 +53,9 @@ declare module 'vue' {
|
||||
NIcon: typeof import('naive-ui')['NIcon']
|
||||
NImage: typeof import('naive-ui')['NImage']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
NLayout: typeof import('naive-ui')['NLayout']
|
||||
NLayoutContent: typeof import('naive-ui')['NLayoutContent']
|
||||
NLayoutHeader: typeof import('naive-ui')['NLayoutHeader']
|
||||
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
||||
NList: typeof import('naive-ui')['NList']
|
||||
@@ -58,9 +65,11 @@ declare module 'vue' {
|
||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||
NModal: typeof import('naive-ui')['NModal']
|
||||
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
||||
NNumberAnimation: typeof import('naive-ui')['NNumberAnimation']
|
||||
NPagination: typeof import('naive-ui')['NPagination']
|
||||
NPopover: typeof import('naive-ui')['NPopover']
|
||||
NProgress: typeof import('naive-ui')['NProgress']
|
||||
NQrCode: typeof import('naive-ui')['NQrCode']
|
||||
NRadio: typeof import('naive-ui')['NRadio']
|
||||
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||
NResult: typeof import('naive-ui')['NResult']
|
||||
@@ -68,7 +77,6 @@ declare module 'vue' {
|
||||
NSelect: typeof import('naive-ui')['NSelect']
|
||||
NSkeleton: typeof import('naive-ui')['NSkeleton']
|
||||
NSlider: typeof import('naive-ui')['NSlider']
|
||||
NSpace: typeof import('naive-ui')['NSpace']
|
||||
NSpin: typeof import('naive-ui')['NSpin']
|
||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||
NTab: typeof import('naive-ui')['NTab']
|
||||
@@ -77,8 +85,10 @@ declare module 'vue' {
|
||||
NTag: typeof import('naive-ui')['NTag']
|
||||
NText: typeof import('naive-ui')['NText']
|
||||
NThing: typeof import('naive-ui')['NThing']
|
||||
NVirtualList: typeof import('naive-ui')['NVirtualList']
|
||||
Pagination: typeof import('./src/components/Global/Pagination.vue')['default']
|
||||
PlayerControl: typeof import('./src/components/Player/PlayerControl.vue')['default']
|
||||
PlayerCover: typeof import('./src/components/Player/PlayerCover.vue')['default']
|
||||
Playlist: typeof import('./src/components/Global/Playlist.vue')['default']
|
||||
PlaylistUpdate: typeof import('./src/components/Modal/PlaylistUpdate.vue')['default']
|
||||
PrivateFm: typeof import('./src/components/Player/PrivateFm.vue')['default']
|
||||
@@ -89,9 +99,11 @@ declare module 'vue' {
|
||||
SearchInp: typeof import('./src/components/Search/SearchInp.vue')['default']
|
||||
SearchSuggestions: typeof import('./src/components/Search/SearchSuggestions.vue')['default']
|
||||
SongList: typeof import('./src/components/List/SongList.vue')['default']
|
||||
SongListDrawer: typeof import('./src/components/List/SongListDrawer.vue')['default']
|
||||
SongListDropdown: typeof import('./src/components/List/SongListDropdown.vue')['default']
|
||||
SpecialCover: typeof import('./src/components/Cover/SpecialCover.vue')['default']
|
||||
SpecialCoverCard: typeof import('./src/components/Cover/SpecialCoverCard.vue')['default']
|
||||
Spectrum: typeof import('./src/components/Player/Spectrum.vue')['default']
|
||||
SvgIcon: typeof import('./src/components/Global/SvgIcon.vue')['default']
|
||||
TitleBar: typeof import('./src/components/WinDom/TitleBar.vue')['default']
|
||||
UpCloudSong: typeof import('./src/components/Modal/UpCloudSong.vue')['default']
|
||||
|
||||
12
docker-compose.yml
Normal file
12
docker-compose.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
services:
|
||||
SPlayer:
|
||||
build:
|
||||
context: .
|
||||
image: splayer
|
||||
container_name: SPlayer
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
ports:
|
||||
- 7899:7899
|
||||
restart: always
|
||||
@@ -19,15 +19,11 @@ asarUnpack:
|
||||
# Windows 平台配置
|
||||
win:
|
||||
# 可执行文件名
|
||||
executableName: splayer
|
||||
executableName: SPlayer
|
||||
# 应用程序的图标文件路径
|
||||
icon: public/images/logo/favicon_256.png
|
||||
icon: public/imgs/icons/favicon-512x512.png
|
||||
# 构建类型
|
||||
target:
|
||||
- nsis
|
||||
- portable
|
||||
# 管理员权限
|
||||
requestedExecutionLevel: highestAvailable
|
||||
target: nsis
|
||||
# NSIS 安装器配置
|
||||
nsis:
|
||||
# 一键式安装程序还是辅助安装程序
|
||||
@@ -44,24 +40,37 @@ nsis:
|
||||
allowElevation: true
|
||||
# 是否允许用户更改安装目录
|
||||
allowToChangeInstallationDirectory: true
|
||||
# 安装包图标
|
||||
installerIcon: public/imgs/icons/favicon.ico
|
||||
# 卸载命令图标
|
||||
uninstallerIcon: public/imgs/icons/favicon.ico
|
||||
# macOS 平台配置
|
||||
mac:
|
||||
# 可执行文件名
|
||||
executableName: splayer
|
||||
executableName: SPlayer
|
||||
# 应用程序的图标文件路径
|
||||
icon: public/images/logo/favicon_512.png
|
||||
icon: public/imgs/icons/favicon-512x512.png
|
||||
# 权限继承的文件路径
|
||||
entitlementsInherit: build/entitlements.mac.plist
|
||||
# 扩展信息,如权限描述
|
||||
extendInfo:
|
||||
- NSCameraUsageDescription: Application requests access to the device's camera.
|
||||
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
||||
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||
NSCameraUsageDescription: Application requests access to the device's camera.
|
||||
NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
||||
NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||
NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||
# 是否启用应用程序的 Notarization(苹果的安全审核)
|
||||
notarize: false
|
||||
darkModeSupport: true
|
||||
category: public.app-category.music
|
||||
target:
|
||||
- target: dmg
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
- target: zip
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
# macOS 平台的 DMG 配置
|
||||
dmg:
|
||||
# DMG 文件的生成名称
|
||||
@@ -69,13 +78,13 @@ dmg:
|
||||
# Linux 平台配置
|
||||
linux:
|
||||
# 可执行文件名
|
||||
executableName: splayer
|
||||
executableName: SPlayer
|
||||
# 应用程序的图标文件路径
|
||||
icon: public/images/logo/favicon_256.png
|
||||
icon: public/imgs/icons/favicon-512x512.png
|
||||
# 构建类型
|
||||
target:
|
||||
- pacman
|
||||
- AppImage
|
||||
- snap
|
||||
- deb
|
||||
- rpm
|
||||
- tar.gz
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { resolve } from "path";
|
||||
import {
|
||||
defineConfig,
|
||||
externalizeDepsPlugin,
|
||||
loadEnv,
|
||||
splitVendorChunkPlugin,
|
||||
} from "electron-vite";
|
||||
import { defineConfig, externalizeDepsPlugin, loadEnv } from "electron-vite";
|
||||
import { NaiveUiResolver } from "unplugin-vue-components/resolvers";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import AutoImport from "unplugin-auto-import/vite";
|
||||
import Components from "unplugin-vue-components/vite";
|
||||
import viteCompression from "vite-plugin-compression";
|
||||
import checkPort from "./electron/main/utils/checkPort";
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
export default defineConfig(async ({ mode }) => {
|
||||
// 读取环境变量
|
||||
const getEnv = (name) => {
|
||||
return loadEnv(mode, process.cwd())[name];
|
||||
};
|
||||
// 获取端口
|
||||
const devPort = await checkPort(getEnv("MAIN_VITE_DEV_PORT"));
|
||||
const serverPort = await checkPort(getEnv("MAIN_VITE_SERVER_PORT"));
|
||||
// 返回配置
|
||||
return {
|
||||
// 主进程
|
||||
@@ -41,7 +41,7 @@ export default defineConfig(({ mode }) => {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: resolve(__dirname, "electron/preload/index.js"),
|
||||
index: resolve(__dirname, "electron/preload/index.mjs"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -69,16 +69,70 @@ export default defineConfig(({ mode }) => {
|
||||
}),
|
||||
// viteCompression
|
||||
viteCompression(),
|
||||
// splitVendorChunkPlugin
|
||||
splitVendorChunkPlugin(),
|
||||
// PWA
|
||||
VitePWA({
|
||||
registerType: "autoUpdate",
|
||||
workbox: {
|
||||
clientsClaim: true,
|
||||
skipWaiting: true,
|
||||
cleanupOutdatedCaches: true,
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: /(.*?)\.(woff2|woff|ttf)/,
|
||||
handler: "CacheFirst",
|
||||
options: {
|
||||
cacheName: "file-cache",
|
||||
},
|
||||
},
|
||||
{
|
||||
urlPattern: /(.*?)\.(webp|png|jpe?g|svg|gif|bmp|psd|tiff|tga|eps)/,
|
||||
handler: "CacheFirst",
|
||||
options: {
|
||||
cacheName: "image-cache",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
manifest: {
|
||||
name: getEnv("RENDERER_VITE_SITE_TITLE"),
|
||||
short_name: getEnv("RENDERER_VITE_SITE_TITLE"),
|
||||
description: getEnv("RENDERER_VITE_SITE_DES"),
|
||||
display: "standalone",
|
||||
start_url: "/",
|
||||
theme_color: "#fff",
|
||||
background_color: "#efefef",
|
||||
icons: [
|
||||
{
|
||||
src: "/imgs/icons/favicon-32x32.png",
|
||||
sizes: "32x32",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "/imgs/icons/favicon-96x96.png",
|
||||
sizes: "96x96",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "/imgs/icons/favicon-256x256.png",
|
||||
sizes: "256x256",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "/imgs/icons/favicon-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
// 服务器配置
|
||||
server: {
|
||||
port: getEnv("MAIN_VITE_DEV_PORT"),
|
||||
port: devPort,
|
||||
// 代理
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: `http://${getEnv("MAIN_VITE_SERVER_HOST")}:${getEnv("MAIN_VITE_SERVER_PORT")}`,
|
||||
target: `http://${getEnv("MAIN_VITE_SERVER_HOST")}:${serverPort}`,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, ""),
|
||||
},
|
||||
@@ -100,9 +154,6 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
},
|
||||
sourcemap: false,
|
||||
win: {
|
||||
icon: resolve(__dirname, "/public/images/logo/favicon.png"),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,12 +1,12 @@
|
||||
import { join } from "path";
|
||||
import { app, protocol, shell, BrowserWindow, globalShortcut } from "electron";
|
||||
import { optimizer, is } from "@electron-toolkit/utils";
|
||||
import { app, protocol, shell, BrowserWindow, globalShortcut, nativeImage } from "electron";
|
||||
import { platform, optimizer, is } from "@electron-toolkit/utils";
|
||||
import { startNcmServer } from "@main/startNcmServer";
|
||||
import { startMainServer } from "@main/startMainServer";
|
||||
import { configureAutoUpdater } from "@main/utils/checkUpdates";
|
||||
import createSystemInfo from "@main/utils/createSystemInfo";
|
||||
import createSystemTray from "@main/utils/createSystemTray";
|
||||
import createGlobalShortcut from "@main/utils/createGlobalShortcut";
|
||||
import mainIpcMain from "@main/mainIpcMain";
|
||||
import Store from "electron-store";
|
||||
import log from "electron-log";
|
||||
|
||||
// 屏蔽报错
|
||||
@@ -14,11 +14,14 @@ process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true";
|
||||
|
||||
// 配置 log
|
||||
log.transports.file.resolvePathFn = () =>
|
||||
join(app.getPath("documents"), "/SPlayer/splayer-log.txt");
|
||||
join(app.getPath("documents"), "/SPlayer/SPlayer-log.txt");
|
||||
// 设置日志文件的最大大小为 2 MB
|
||||
log.transports.file.maxSize = 2 * 1024 * 1024;
|
||||
// 绑定 console.log
|
||||
console.log = log.log.bind(log);
|
||||
// 绑定 console 事件
|
||||
console.error = log.error.bind(log);
|
||||
console.warn = log.warn.bind(log);
|
||||
console.info = log.info.bind(log);
|
||||
console.debug = log.debug.bind(log);
|
||||
|
||||
// 主进程
|
||||
class MainProcess {
|
||||
@@ -29,25 +32,59 @@ class MainProcess {
|
||||
this.mainServer = null;
|
||||
// 网易云 API
|
||||
this.ncmServer = null;
|
||||
// Store
|
||||
this.store = new Store({
|
||||
// 窗口大小
|
||||
windowSize: {
|
||||
width: { type: "number", default: 1280 },
|
||||
height: { type: "number", default: 740 },
|
||||
},
|
||||
});
|
||||
// 设置应用程序名称
|
||||
if (process.platform === "win32") app.setAppUserModelId(app.getName());
|
||||
// 初始化
|
||||
this.init();
|
||||
this.checkApp().then(async (lockObtained) => {
|
||||
if (lockObtained) {
|
||||
await this.init();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 单例锁
|
||||
async checkApp() {
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
log.error("已有一个程序正在运行,本次启动阻止");
|
||||
app.quit();
|
||||
// 未获得锁
|
||||
return false;
|
||||
}
|
||||
// 聚焦到当前程序
|
||||
else {
|
||||
app.on("second-instance", () => {
|
||||
if (this.mainWindow) {
|
||||
this.mainWindow.show();
|
||||
if (this.mainWindow.isMinimized()) this.mainWindow.restore();
|
||||
this.mainWindow.focus();
|
||||
}
|
||||
});
|
||||
// 获得锁
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化程序
|
||||
async init() {
|
||||
log.info("主进程初始化");
|
||||
|
||||
// 单例锁
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.quit();
|
||||
log.error("已有一个程序正在运行,本次启动阻止");
|
||||
}
|
||||
|
||||
// 启动网易云 API
|
||||
try {
|
||||
this.ncmServer = await startNcmServer({
|
||||
port: import.meta.env.MAIN_VITE_SERVER_PORT,
|
||||
host: import.meta.env.MAIN_VITE_SERVER_HOST,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("启动网易云 API 失败:", error);
|
||||
}
|
||||
|
||||
// 非开发环境启动代理
|
||||
if (!is.dev) {
|
||||
@@ -55,7 +92,7 @@ class MainProcess {
|
||||
}
|
||||
|
||||
// 注册应用协议
|
||||
app.setAsDefaultProtocolClient("splayer");
|
||||
app.setAsDefaultProtocolClient("SPlayer");
|
||||
// 应用程序准备好之前注册
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{ scheme: "app", privileges: { secure: true, standard: true } },
|
||||
@@ -69,21 +106,23 @@ class MainProcess {
|
||||
createWindow() {
|
||||
// 创建浏览器窗口
|
||||
this.mainWindow = new BrowserWindow({
|
||||
width: 1280, // 窗口宽度
|
||||
height: 720, // 窗口高度
|
||||
title: app.getName() || "SPlayer",
|
||||
width: this.store.get("windowSize.width") || 1280, // 窗口宽度
|
||||
height: this.store.get("windowSize.height") || 740, // 窗口高度
|
||||
minHeight: 700, // 最小高度
|
||||
minWidth: 1200, // 最小宽度
|
||||
center: true, // 是否出现在屏幕居中的位置
|
||||
show: false, // 初始时不显示窗口
|
||||
frame: false, // 无边框
|
||||
// transparent: true, // 透明窗口
|
||||
titleBarStyle: "customButtonsOnHover", // Macos 隐藏菜单栏
|
||||
autoHideMenuBar: true, // 失去焦点后自动隐藏菜单栏
|
||||
// 图标配置
|
||||
icon: join(__dirname, "../../public/images/logo/favicon.png"),
|
||||
icon: nativeImage.createFromPath(join(__dirname, "../../public/imgs/icons/favicon.png")),
|
||||
// 预加载
|
||||
webPreferences: {
|
||||
// devTools: is.dev, //是否开启 DevTools
|
||||
preload: join(__dirname, "../preload/index.js"),
|
||||
// devTools: is.dev,
|
||||
preload: join(__dirname, "../preload/index.mjs"),
|
||||
sandbox: false,
|
||||
webSecurity: false,
|
||||
hardwareAcceleration: true,
|
||||
@@ -91,9 +130,10 @@ class MainProcess {
|
||||
});
|
||||
|
||||
// 窗口准备就绪时显示窗口
|
||||
this.mainWindow.on("ready-to-show", () => {
|
||||
this.mainWindow.once("ready-to-show", () => {
|
||||
this.mainWindow.show();
|
||||
// mainWindow.maximize();
|
||||
this.store.set("windowSize", this.mainWindow.getBounds());
|
||||
});
|
||||
|
||||
// 主窗口事件
|
||||
@@ -112,53 +152,56 @@ class MainProcess {
|
||||
}
|
||||
// 生产模式
|
||||
else {
|
||||
this.mainWindow.loadURL(`http://127.0.0.1:${import.meta.env.MAIN_VITE_MAIN_PORT ?? 7899}`);
|
||||
console.log("生产模式渲染端口: " + process.env.MAIN_VITE_MAIN_PORT ?? 7899);
|
||||
this.mainWindow.loadURL(`http://127.0.0.1:${process.env.MAIN_VITE_MAIN_PORT ?? 7899}`);
|
||||
}
|
||||
|
||||
// 监听关闭
|
||||
this.mainWindow.on("close", (event) => {
|
||||
if (!app.isQuiting) {
|
||||
event.preventDefault();
|
||||
this.mainWindow.hide();
|
||||
}
|
||||
return false;
|
||||
// 配置网络代理
|
||||
const proxyRules = this.store.get("proxy");
|
||||
if (proxyRules) {
|
||||
this.mainWindow.webContents.session.setProxy({ proxyRules }, (result) => {
|
||||
console.info("网络代理配置:", result);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 主应用程序事件
|
||||
mainAppEvents() {
|
||||
app.on("ready", async () => {
|
||||
app.whenReady().then(async () => {
|
||||
// 创建主窗口
|
||||
this.createWindow();
|
||||
// 检测更新
|
||||
configureAutoUpdater(process.platform);
|
||||
// 创建系统信息
|
||||
createSystemInfo(this.mainWindow);
|
||||
// 引入主 Ipc
|
||||
mainIpcMain(this.mainWindow);
|
||||
mainIpcMain(this.mainWindow, this.store);
|
||||
// 系统托盘
|
||||
createSystemTray(this.mainWindow);
|
||||
// 注册快捷键
|
||||
createGlobalShortcut(this.mainWindow);
|
||||
});
|
||||
// 在开发模式下默认通过 F12 打开或关闭 DevTools
|
||||
|
||||
// 开发环境下 F12 打开控制台
|
||||
app.on("browser-window-created", (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window);
|
||||
});
|
||||
|
||||
// 在 macOS 上,当单击 Dock 图标且没有其他窗口时,通常会重新创建窗口
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) this.createWindow();
|
||||
});
|
||||
|
||||
// 自定义协议
|
||||
app.on("open-url", (_, url) => {
|
||||
console.log("Received custom protocol URL:", url);
|
||||
});
|
||||
|
||||
// 将要退出
|
||||
app.on("will-quit", () => {
|
||||
// 注销全部快捷键
|
||||
globalShortcut.unregisterAll();
|
||||
});
|
||||
|
||||
// 当所有窗口都关闭时退出应用,macOS 除外
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") {
|
||||
if (!platform.isMacOS) {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
@@ -167,19 +210,46 @@ class MainProcess {
|
||||
// 主窗口事件
|
||||
mainWindowEvents() {
|
||||
this.mainWindow.on("show", () => {
|
||||
console.info("窗口展示");
|
||||
this.mainWindow.webContents.send("lyricsScroll");
|
||||
});
|
||||
|
||||
// this.mainWindow.on("hide", () => {
|
||||
// console.info("窗口隐藏");
|
||||
// });
|
||||
|
||||
this.mainWindow.on("focus", () => {
|
||||
console.info("窗口获得焦点");
|
||||
this.mainWindow.webContents.send("lyricsScroll");
|
||||
});
|
||||
|
||||
// this.mainWindow.on("blur", () => {
|
||||
// console.info("窗口失去焦点");
|
||||
// });
|
||||
|
||||
this.mainWindow.on("maximize", () => {
|
||||
this.mainWindow.webContents.send("windowState", true);
|
||||
});
|
||||
|
||||
this.mainWindow.on("unmaximize", () => {
|
||||
this.mainWindow.webContents.send("windowState", false);
|
||||
});
|
||||
|
||||
this.mainWindow.on("resize", () => {
|
||||
this.store.set("windowSize", this.mainWindow.getBounds());
|
||||
});
|
||||
|
||||
this.mainWindow.on("move", () => {
|
||||
this.store.set("windowSize", this.mainWindow.getBounds());
|
||||
});
|
||||
|
||||
// 窗口关闭
|
||||
this.mainWindow.on("close", (event) => {
|
||||
event.preventDefault();
|
||||
if (!app.isQuiting) {
|
||||
this.mainWindow.hide();
|
||||
} else {
|
||||
app.exit();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { ipcMain, dialog, app, clipboard, shell } from "electron";
|
||||
import { File, Picture, Id3v2Settings } from "node-taglib-sharp";
|
||||
import { configureAutoUpdater } from "@main/utils/checkUpdates";
|
||||
import { readDirAsync } from "@main/utils/readDirAsync";
|
||||
import { parseFile } from "music-metadata";
|
||||
import { write } from "node-id3";
|
||||
import { download } from "electron-dl";
|
||||
import { getFonts } from "font-list";
|
||||
import getNeteaseMusicUrl from "@main/utils/getNeteaseMusicUrl";
|
||||
import axios from "axios";
|
||||
import fs from "fs/promises";
|
||||
@@ -10,9 +12,10 @@ import fs from "fs/promises";
|
||||
/**
|
||||
* 监听主进程的 IPC 事件
|
||||
* @param {BrowserWindow} win - 要监听 IPC 事件的程序窗口
|
||||
* @param {Store} store - 存储对象
|
||||
*/
|
||||
|
||||
const mainIpcMain = (win) => {
|
||||
const mainIpcMain = (win, store) => {
|
||||
// 窗口操作部分
|
||||
ipcMain.on("window-min", (ev) => {
|
||||
// 阻止最小化
|
||||
@@ -23,7 +26,7 @@ const mainIpcMain = (win) => {
|
||||
ipcMain.on("window-maxOrRestore", (ev) => {
|
||||
const winSizeState = win.isMaximized();
|
||||
winSizeState ? win.restore() : win.maximize();
|
||||
ev.reply("window-state", win.isMaximized());
|
||||
ev.reply("windowState", win.isMaximized());
|
||||
});
|
||||
ipcMain.on("window-restore", () => {
|
||||
win.restore();
|
||||
@@ -41,6 +44,10 @@ const mainIpcMain = (win) => {
|
||||
app.relaunch();
|
||||
app.quit();
|
||||
});
|
||||
ipcMain.on("check-updates", () => {
|
||||
console.info("开始检查更新");
|
||||
configureAutoUpdater();
|
||||
});
|
||||
|
||||
// 显示进度
|
||||
ipcMain.on("setProgressBar", (_, val) => {
|
||||
@@ -193,32 +200,39 @@ const mainIpcMain = (win) => {
|
||||
});
|
||||
|
||||
// 下载文件至指定目录
|
||||
ipcMain.handle("downloadFile", async (_, data, song, songName, songType, path) => {
|
||||
ipcMain.handle("downloadFile", async (_, songData, options) => {
|
||||
try {
|
||||
const { url, data, lyric, name, type } = JSON.parse(songData);
|
||||
const { path, downloadMeta, downloadCover, downloadLyrics } = JSON.parse(options);
|
||||
if (fs.access(path)) {
|
||||
const songData = JSON.parse(song);
|
||||
console.info("开始下载:", songData, data);
|
||||
console.info("开始下载:", name, url);
|
||||
// 下载歌曲
|
||||
const songDownload = await download(win, data.url, {
|
||||
const songDownload = await download(win, url, {
|
||||
directory: path,
|
||||
filename: `${songName}.${songType}`,
|
||||
filename: `${name}.${type}`,
|
||||
});
|
||||
// 若关闭,则不进行元信息写入
|
||||
if (!downloadMeta) return true;
|
||||
// 下载封面
|
||||
const coverDownload = await download(win, songData.cover, {
|
||||
const coverDownload = await download(win, data.cover, {
|
||||
directory: path,
|
||||
filename: `${songName}.jpg`,
|
||||
filename: `${name}.jpg`,
|
||||
});
|
||||
// 生成歌曲文件的元数据
|
||||
const songTag = {
|
||||
title: songData.name,
|
||||
artist: Array.isArray(songData.artists)
|
||||
? songData.artists.map((ar) => ar.name).join(" / ")
|
||||
: songData.artists || "未知歌手",
|
||||
album: songData.album?.name || songData.album,
|
||||
image: coverDownload.getSavePath(),
|
||||
};
|
||||
// 读取歌曲文件
|
||||
const songFile = File.createFromPath(songDownload.getSavePath());
|
||||
// 生成图片信息
|
||||
const songCover = Picture.fromPath(coverDownload.getSavePath());
|
||||
// 保存修改后的元数据
|
||||
write(songTag, songDownload.getSavePath());
|
||||
Id3v2Settings.forceDefaultVersion = true;
|
||||
Id3v2Settings.defaultVersion = 3;
|
||||
songFile.tag.title = data.name || "未知曲目";
|
||||
songFile.tag.album = data.album?.name || "未知专辑";
|
||||
songFile.tag.performers = data?.artists?.map((ar) => ar.name) || ["未知艺术家"];
|
||||
if (downloadLyrics) songFile.tag.lyrics = lyric;
|
||||
if (downloadCover) songFile.tag.pictures = [songCover];
|
||||
// 保存元信息
|
||||
songFile.save();
|
||||
songFile.dispose();
|
||||
// 删除封面
|
||||
await fs.unlink(coverDownload.getSavePath());
|
||||
return true;
|
||||
@@ -231,6 +245,35 @@ const mainIpcMain = (win) => {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// 读取系统全部字体
|
||||
ipcMain.handle("getAllFonts", async () => {
|
||||
try {
|
||||
const fonts = await getFonts();
|
||||
return fonts;
|
||||
} catch (error) {
|
||||
console.error("获取系统字体时出错:", error);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
// 配置网络代理
|
||||
ipcMain.on("set-proxy", (_, config) => {
|
||||
console.log(config);
|
||||
const proxyRules = `${config.protocol}://${config.server}:${config.port}`;
|
||||
store.set("proxy", proxyRules);
|
||||
win.webContents.session.setProxy({ proxyRules }, () => {
|
||||
console.info("网络代理配置完成");
|
||||
});
|
||||
});
|
||||
|
||||
// 取消代理
|
||||
ipcMain.on("remove-proxy", () => {
|
||||
store.set("proxy", "");
|
||||
win.webContents.session.setProxy({ proxyRules: "" }, () => {
|
||||
console.info("取消网络代理配置");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { join } from "path";
|
||||
import express from "express";
|
||||
import expressProxy from "express-http-proxy";
|
||||
import checkPort from "./utils/checkPort";
|
||||
|
||||
/**
|
||||
* 启动主服务器
|
||||
@@ -8,12 +9,14 @@ import expressProxy from "express-http-proxy";
|
||||
*/
|
||||
export const startMainServer = async () => {
|
||||
const { MAIN_VITE_MAIN_PORT, MAIN_VITE_SERVER_HOST, MAIN_VITE_SERVER_PORT } = import.meta.env;
|
||||
const port = MAIN_VITE_MAIN_PORT ?? 7899;
|
||||
const port = await checkPort(MAIN_VITE_MAIN_PORT ?? 7899);
|
||||
process.env.MAIN_VITE_MAIN_PORT = port;
|
||||
const apiHost = `http://${MAIN_VITE_SERVER_HOST}:${MAIN_VITE_SERVER_PORT}`;
|
||||
const expressApp = express();
|
||||
// 代理
|
||||
expressApp.use("/", express.static(join(__dirname, "../renderer/")));
|
||||
expressApp.use("/api", expressProxy(apiHost));
|
||||
console.log("生产模式主进程端口: ", port);
|
||||
// 启动 Express 应用服务器,并监听指定端口
|
||||
return expressApp.listen(port, "127.0.0.1");
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
const netEaseApi = require("NeteaseCloudMusicApi");
|
||||
import netEaseApi from "NeteaseCloudMusicApi";
|
||||
import checkPort from "@main/utils/checkPort";
|
||||
|
||||
/**
|
||||
* 启动网易云音乐 API 服务器
|
||||
*
|
||||
* @async
|
||||
* @param {Object} options - 服务器配置
|
||||
* @param {number} [options.port=12141] - 服务器端口
|
||||
* @param {number} [options.port=11451] - 服务器端口
|
||||
* @param {string} [options.host="127.0.0.1"] - 服务器主机地址
|
||||
* @returns {Promise<void>} 返回一个 Promise,在 API 服务器成功启动后 resolve
|
||||
*/
|
||||
@@ -15,6 +16,7 @@ export const startNcmServer = async (
|
||||
host: "127.0.0.1",
|
||||
},
|
||||
) => {
|
||||
console.log(options);
|
||||
const serverPort = await checkPort(options.port);
|
||||
options.port = serverPort;
|
||||
return await netEaseApi.serveNcmApi(options);
|
||||
};
|
||||
|
||||
37
electron/main/utils/checkPort.js
Normal file
37
electron/main/utils/checkPort.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import net from "net";
|
||||
|
||||
/**
|
||||
* 检查端口是否可用, 如果被占用或不可访问,则尝试下一个端口
|
||||
* @param {number} port 端口号
|
||||
* @param {number} [maxPort=65535] 端口号上限
|
||||
* @returns {Promise<number>} 返回可用的端口号
|
||||
*/
|
||||
const checkPort = (port, maxPort = 65535) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (port > maxPort) {
|
||||
reject(new Error(`${port} 超出端口范围,无法找到可用端口`));
|
||||
return;
|
||||
}
|
||||
|
||||
port = Number(port);
|
||||
|
||||
const server = net.createServer();
|
||||
|
||||
server.listen(port, "0.0.0.0", () => {
|
||||
server.once("close", () => {
|
||||
resolve(port);
|
||||
});
|
||||
server.close();
|
||||
});
|
||||
|
||||
server.on("error", (err) => {
|
||||
if (err.code === "EADDRINUSE" || err.code === "EACCES") {
|
||||
resolve(checkPort(port + 1, maxPort));
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export default checkPort;
|
||||
@@ -1,6 +1,8 @@
|
||||
import { dialog, shell } from "electron";
|
||||
import { dialog } from "electron";
|
||||
import { is } from "@electron-toolkit/utils";
|
||||
import { autoUpdater } from "electron-updater";
|
||||
import pkg from "electron-updater";
|
||||
|
||||
const { autoUpdater } = pkg;
|
||||
|
||||
// 更新弹窗
|
||||
const hasNewVersion = (info) => {
|
||||
@@ -8,23 +10,56 @@ const hasNewVersion = (info) => {
|
||||
.showMessageBox({
|
||||
title: "发现新版本 v" + info.version,
|
||||
message: "发现新版本 v" + info.version,
|
||||
detail: "是否前往 GitHub 下载新版本安装包?",
|
||||
buttons: ["前往", "取消"],
|
||||
detail: "是否立即下载并安装新版本?",
|
||||
buttons: ["立即下载", "取消"],
|
||||
type: "question",
|
||||
noLink: true,
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.response === 0) {
|
||||
shell.openExternal("https://github.com/imsyy/SPlayer/releases");
|
||||
// 触发手动下载
|
||||
autoUpdater.downloadUpdate();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const configureAutoUpdater = () => {
|
||||
if (is.dev) return false;
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
|
||||
// 监听下载进度事件
|
||||
autoUpdater.on("download-progress", (progressObj) => {
|
||||
console.log(`更新下载进度: ${progressObj.percent}%`);
|
||||
});
|
||||
|
||||
// 下载完成
|
||||
autoUpdater.on("update-downloaded", () => {
|
||||
// 显示安装弹窗
|
||||
dialog
|
||||
.showMessageBox({
|
||||
title: "下载完成",
|
||||
message: "新版本已下载完成,是否现在安装?",
|
||||
buttons: ["是", "稍后"],
|
||||
type: "question",
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.response === 0) {
|
||||
// 安装更新
|
||||
autoUpdater.quitAndInstall();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 下载失败
|
||||
autoUpdater.on("error", (err) => {
|
||||
console.error("下载更新失败:", err);
|
||||
dialog.showErrorBox("下载更新失败", "请检查网络连接并稍后重试!");
|
||||
});
|
||||
|
||||
// 若有更新
|
||||
autoUpdater.on("update-available", (info) => {
|
||||
hasNewVersion(info);
|
||||
});
|
||||
|
||||
// 检查更新
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
};
|
||||
|
||||
@@ -6,8 +6,18 @@ import { globalShortcut } from "electron";
|
||||
*/
|
||||
const createGlobalShortcut = (win) => {
|
||||
// 刷新程序
|
||||
globalShortcut.register("CommandOrControl+R", () => {
|
||||
win?.reload();
|
||||
globalShortcut.register("CmdOrCtrl+Shift+R", () => {
|
||||
if (win && win.isFocused()) win?.reload();
|
||||
});
|
||||
|
||||
// 打开开发者工具
|
||||
globalShortcut.register("CmdOrCtrl+Shift+I", () => {
|
||||
if (win && win.isFocused()) {
|
||||
win?.webContents.openDevTools({
|
||||
mode: "right",
|
||||
activate: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,59 +1,78 @@
|
||||
import { join } from "path";
|
||||
import { Tray, Menu, app, ipcMain, nativeImage, nativeTheme } from "electron";
|
||||
import { join } from "path";
|
||||
|
||||
// 当前播放歌曲数据
|
||||
// 当前歌曲数据
|
||||
let playSongName = "当前暂无播放歌曲";
|
||||
let playSongState = false;
|
||||
|
||||
/**
|
||||
* 创建系统自定义信息
|
||||
* 创建系统托盘
|
||||
* @param {BrowserWindow} win - 程序窗口
|
||||
*/
|
||||
const createSystemInfo = (win) => {
|
||||
// 弹出列表
|
||||
app.setUserTasks([]);
|
||||
const createSystemTray = (win) => {
|
||||
// 系统托盘
|
||||
const mainTray = new Tray(join(__dirname, "../../public/images/logo/favicon.png"));
|
||||
// 给托盘图标设置气球提示
|
||||
const mainTray = new Tray(
|
||||
nativeImage
|
||||
.createFromPath(
|
||||
join(
|
||||
__dirname,
|
||||
process.platform === "win32"
|
||||
? "../../public/imgs/icons/favicon.ico"
|
||||
: "../../public/imgs/icons/favicon-32x32.png",
|
||||
),
|
||||
)
|
||||
.resize({
|
||||
height: 32,
|
||||
width: 32,
|
||||
}),
|
||||
);
|
||||
// 应用内菜单
|
||||
Menu.setApplicationMenu(createTrayMenu(win));
|
||||
// 默认名称
|
||||
win.setTitle(app.getName());
|
||||
mainTray.setTitle(app.getName());
|
||||
mainTray.setToolTip(app.getName());
|
||||
// 歌曲数据改变时
|
||||
// 左键事件
|
||||
mainTray.on("click", () => win.show());
|
||||
// 托盘菜单
|
||||
mainTray.setContextMenu(createTrayMenu(win));
|
||||
// 系统主题改变
|
||||
nativeTheme.on("updated", () => {
|
||||
mainTray.setContextMenu(createTrayMenu(win));
|
||||
});
|
||||
// 播放歌曲改变
|
||||
ipcMain.on("songNameChange", (_, val) => {
|
||||
playSongName = val;
|
||||
// 托盘图标标题
|
||||
mainTray.setToolTip(val);
|
||||
// 更改应用标题
|
||||
win.setTitle(val);
|
||||
mainTray.setTitle(val);
|
||||
mainTray.setToolTip(val);
|
||||
mainTray.setContextMenu(createTrayMenu(win));
|
||||
});
|
||||
// 播放状态改变
|
||||
ipcMain.on("songStateChange", (_, val) => {
|
||||
playSongState = val;
|
||||
mainTray.setContextMenu(createTrayMenu(win));
|
||||
});
|
||||
// 左键事件
|
||||
mainTray.on("click", () => {
|
||||
// 显示窗口
|
||||
win.show();
|
||||
});
|
||||
// 右键事件
|
||||
mainTray.on("right-click", () => {
|
||||
mainTray.popUpContextMenu(Menu.buildFromTemplate(createTrayMenu(win)));
|
||||
});
|
||||
};
|
||||
|
||||
// 生成图标
|
||||
const createIcon = (name) => {
|
||||
// 系统是否为暗色
|
||||
const isDarkMode = nativeTheme.shouldUseDarkColors;
|
||||
// 返回图标
|
||||
return nativeImage
|
||||
.createFromPath(
|
||||
isDarkMode
|
||||
? join(__dirname, `../../public/imgs/icons/${name}-dark.png`)
|
||||
: join(__dirname, `../../public/imgs/icons/${name}-light.png`),
|
||||
)
|
||||
.resize({ width: 16, height: 16 });
|
||||
};
|
||||
|
||||
// 生成右键菜单
|
||||
const createTrayMenu = (win) => {
|
||||
// 系统是否为暗色
|
||||
const isDarkMode = nativeTheme.shouldUseDarkColors;
|
||||
// 生成图标
|
||||
const createIcon = (name) => {
|
||||
return nativeImage
|
||||
.createFromPath(
|
||||
isDarkMode
|
||||
? join(__dirname, `../../public/images/icon/${name}-dark.png`)
|
||||
: join(__dirname, `../../public/images/icon/${name}-light.png`),
|
||||
)
|
||||
.resize({ width: 16, height: 16 });
|
||||
};
|
||||
// 返回菜单
|
||||
return [
|
||||
return Menu.buildFromTemplate([
|
||||
{
|
||||
label: playSongName,
|
||||
icon: createIcon("open"),
|
||||
@@ -69,6 +88,7 @@ const createTrayMenu = (win) => {
|
||||
{
|
||||
label: "上一曲",
|
||||
icon: createIcon("prev"),
|
||||
accelerator: "CmdOrCtrl+Left",
|
||||
click: () => {
|
||||
win.webContents.send("playNextOrPrev", "prev");
|
||||
},
|
||||
@@ -76,6 +96,7 @@ const createTrayMenu = (win) => {
|
||||
{
|
||||
label: playSongState ? "暂停" : "播放",
|
||||
icon: createIcon(playSongState ? "pause" : "play"),
|
||||
accelerator: "CmdOrCtrl+Space",
|
||||
click: () => {
|
||||
win.webContents.send("playOrPause");
|
||||
},
|
||||
@@ -83,6 +104,7 @@ const createTrayMenu = (win) => {
|
||||
{
|
||||
label: "下一曲",
|
||||
icon: createIcon("next"),
|
||||
accelerator: "CmdOrCtrl+Right",
|
||||
click: () => {
|
||||
win.webContents.send("playNextOrPrev", "next");
|
||||
},
|
||||
@@ -94,7 +116,9 @@ const createTrayMenu = (win) => {
|
||||
label: "全局设置",
|
||||
icon: createIcon("setting"),
|
||||
click: () => {
|
||||
win.webContents.send("setting");
|
||||
win.show();
|
||||
win.focus();
|
||||
win.webContents.send("open-setting");
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -109,7 +133,7 @@ const createTrayMenu = (win) => {
|
||||
app.quit();
|
||||
},
|
||||
},
|
||||
];
|
||||
]);
|
||||
};
|
||||
|
||||
export default createSystemInfo;
|
||||
export default createSystemTray;
|
||||
@@ -121,7 +121,7 @@ const getKuwoSongUrl = async (keyword) => {
|
||||
const url = encryptQuery
|
||||
? "http://mobi.kuwo.cn/mobi.s?f=kuwo&q=" +
|
||||
encryptQuery(
|
||||
"corp=kuwo&source=kwplayer_ar_8.5.5.0_apk_keluze.apk&p2p=1&type=convert_url2&sig=0&format=mp3" +
|
||||
"corp=kuwo&source=kwplayer_ar_5.1.0.0_B_jiakong_vh.apk&p2p=1&type=convert_url2&sig=0&format=mp3" +
|
||||
"&rid=" +
|
||||
songId,
|
||||
)
|
||||
|
||||
19
index.html
19
index.html
@@ -1,24 +1,23 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="%RENDERER_VITE_SITE_LOGO%" />
|
||||
<link rel="apple-touch-icon" href="%RENDERER_VITE_SITE_APPLE_LOGO%" />
|
||||
<link rel="bookmark" href="%RENDERER_VITE_SITE_APPLE_LOGO%" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="200x200" href="%RENDERER_VITE_SITE_APPLE_LOGO%" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/imgs/icons/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/imgs/icons/favicon-16x16.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/imgs/icons/apple-touch-icon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>%RENDERER_VITE_SITE_TITLE%</title>
|
||||
<meta name="apple-mobile-web-app-title" content="%RENDERER_VITE_SITE_TITLE%" />
|
||||
<meta name="author" content="%RENDERER_VITE_SITE_ANTHOR%" />
|
||||
<meta name="keywords" content="%RENDERER_VITE_SITE_KEYWORDS%" />
|
||||
<meta name="description" content="%RENDERER_VITE_SITE_DES%" />
|
||||
<link rel="mask-icon" href="/imgs/icons/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
28
nginx.conf
Normal file
28
nginx.conf
Normal file
@@ -0,0 +1,28 @@
|
||||
server {
|
||||
gzip on;
|
||||
listen 7899;
|
||||
listen [::]:7899;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location @rewrites {
|
||||
rewrite ^(.*)$ /index.html last;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_buffers 16 32k;
|
||||
proxy_buffer_size 128k;
|
||||
proxy_busy_buffers_size 128k;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Host $remote_addr;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
proxy_pass http://localhost:3000/;
|
||||
}
|
||||
}
|
||||
81
package.json
81
package.json
@@ -1,19 +1,25 @@
|
||||
{
|
||||
"name": "splayer",
|
||||
"version": "2.0.0-beta.3",
|
||||
"version": "2.1.0",
|
||||
"description": "A minimalist music player",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "imsyy",
|
||||
"home": "https://imsyy.top",
|
||||
"github": "https://github.com/imsyy/SPlayer",
|
||||
"repository": "github:imsyy/SPlayer",
|
||||
"license": "AGPL-3.0",
|
||||
"license-file": "LICENSE",
|
||||
"engines": {
|
||||
"node": ">=16.16.0"
|
||||
"node": ">=18.16.0",
|
||||
"npm": ">=9.6.7",
|
||||
"pnpm": ">=8.14.0"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"format": "prettier --write .",
|
||||
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix",
|
||||
"start": "electron-vite preview",
|
||||
"dev": "chcp 65001 && electron-vite dev --watch",
|
||||
"dev": "electron-vite dev --watch",
|
||||
"build": "electron-vite build",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"build:win": "npm run build && electron-builder --win --config",
|
||||
@@ -21,48 +27,51 @@
|
||||
"build:linux": "npm run build && electron-builder --linux --config"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron-toolkit/preload": "^2.0.0",
|
||||
"@electron-toolkit/utils": "^2.0.0",
|
||||
"@electron-toolkit/preload": "^3.0.1",
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@material/material-color-utilities": "^0.2.7",
|
||||
"NeteaseCloudMusicApi": "^4.13.5",
|
||||
"axios": "^1.4.0",
|
||||
"NeteaseCloudMusicApi": "^4.19.9",
|
||||
"axios": "^1.7.2",
|
||||
"colorthief": "^2.4.0",
|
||||
"electron-dl": "^3.5.1",
|
||||
"electron-updater": "^6.1.7",
|
||||
"express": "^4.18.2",
|
||||
"express-http-proxy": "^1.6.3",
|
||||
"howler": "^2.2.3",
|
||||
"electron-dl": "^3.5.2",
|
||||
"electron-store": "^8.2.0",
|
||||
"electron-updater": "^6.2.1",
|
||||
"express": "^4.19.2",
|
||||
"express-http-proxy": "^2.0.0",
|
||||
"font-list": "^1.5.1",
|
||||
"howler": "^2.2.4",
|
||||
"js-cookie": "^3.0.5",
|
||||
"localforage": "^1.10.0",
|
||||
"music-metadata": "7.13.4",
|
||||
"node-id3": "^0.2.6",
|
||||
"pinia": "^2.1.6",
|
||||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"music-metadata": "7.14.0",
|
||||
"node-taglib-sharp": "^5.2.3",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"plyr": "^3.7.8",
|
||||
"qrcode.vue": "^3.4.1",
|
||||
"screenfull": "^6.0.2",
|
||||
"vue-router": "^4.2.4",
|
||||
"vue-router": "^4.3.3",
|
||||
"vue-slider-component": "4.1.0-beta.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-toolkit/eslint-config": "^1.0.1",
|
||||
"@rushstack/eslint-patch": "^1.3.3",
|
||||
"@vitejs/plugin-vue": "^4.3.1",
|
||||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
"electron": "^25.6.0",
|
||||
"electron-builder": "^24.6.4",
|
||||
"electron-log": "^5.0.1",
|
||||
"electron-vite": "^1.0.27",
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"naive-ui": "^2.34.4",
|
||||
"prettier": "^3.0.2",
|
||||
"sass": "^1.66.1",
|
||||
"terser": "^5.19.2",
|
||||
"unplugin-auto-import": "^0.16.6",
|
||||
"unplugin-vue-components": "^0.25.1",
|
||||
"vite": "^4.4.9",
|
||||
"@electron-toolkit/eslint-config": "^1.0.2",
|
||||
"@rushstack/eslint-patch": "^1.10.3",
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"@vue/eslint-config-prettier": "^9.0.0",
|
||||
"ajv": "^8.16.0",
|
||||
"electron": "^28.3.3",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-log": "^5.1.5",
|
||||
"electron-vite": "^2.2.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-vue": "^9.26.0",
|
||||
"naive-ui": "^2.38.2",
|
||||
"prettier": "^3.3.1",
|
||||
"sass": "^1.77.4",
|
||||
"terser": "^5.31.1",
|
||||
"unplugin-auto-import": "^0.17.6",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vite": "^5.2.13",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vue": "^3.3.4"
|
||||
"vite-plugin-pwa": "^0.17.5",
|
||||
"vue": "3.4.8"
|
||||
}
|
||||
}
|
||||
|
||||
5386
pnpm-lock.yaml
generated
5386
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
BIN
public/font/HarmonyOS_Sans_SC.woff2
Normal file
BIN
public/font/HarmonyOS_Sans_SC.woff2
Normal file
Binary file not shown.
BIN
public/font/HarmonyOS_Sans_SC_Bold.woff2
Normal file
BIN
public/font/HarmonyOS_Sans_SC_Bold.woff2
Normal file
Binary file not shown.
13
public/font/font.css
Normal file
13
public/font/font.css
Normal file
@@ -0,0 +1,13 @@
|
||||
@font-face {
|
||||
font-family: "HarmonyOS Sans";
|
||||
src: url("./HarmonyOS_Sans_SC.woff2") format("woff2");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "HarmonyOS Sans";
|
||||
src: url("./HarmonyOS_Sans_SC_Bold.woff2") format("woff2");
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
1
public/font/font.min.css
vendored
1
public/font/font.min.css
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.7 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user