diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..03f2450
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,16 @@
+node_modules
+npm-debug.log
+Dockerfile*
+docker-compose*
+.dockerignore
+.git
+.github
+.gitignore
+README.md
+LICENSE
+.vscode
+dist
+dist_electron
+build
+images
+script
\ No newline at end of file
diff --git a/.env.example b/.env.example
index bd591ef..7c0058a 100644
--- a/.env.example
+++ b/.env.example
@@ -1,5 +1,7 @@
-VUE_APP_NETEASE_API_URL=http://127.0.0.1:3000
+VUE_APP_NETEASE_API_URL=/api
VUE_APP_ELECTRON_API_URL=/api
-VUE_APP_ELECTRON_API_URL_DEV=http://127.0.0.1:3000
-VUE_APP_ENABLE_SENTRY=false
-DEV_SERVER_PORT=20201
\ No newline at end of file
+VUE_APP_ELECTRON_API_URL_DEV=http://127.0.0.1:10754
+VUE_APP_LASTFM_API_KEY=09c55292403d961aa517ff7f5e8a3d9c
+VUE_APP_LASTFM_API_SHARED_SECRET=307c9fda32b3904e53654baff215cb67
+DEV_SERVER_PORT=20201
+
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..fd24c66
--- /dev/null
+++ b/.envrc
@@ -0,0 +1,4 @@
+source_url "https://raw.githubusercontent.com/cachix/devenv/82c0147677e510b247d8b9165c54f73d32dfd899/direnvrc" "sha256-7u4iDd1nZpxL4tCzmPG0dQgC5V+/44Ba+tHkPob1v2k="
+
+export NIXPKGS_ALLOW_INSECURE=1
+use devenv
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..ac10488
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,7 @@
+* text eol=lf
+# Denote all files that are truly binary and should not be modified.
+*.png binary
+*.jpg binary
+*.mp3 binary
+*.icns binary
+*.gif binary
diff --git a/.github/ISSUE_TEMPLATE/----------.md b/.github/ISSUE_TEMPLATE/----------.md
new file mode 100644
index 0000000..ba42d00
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/----------.md
@@ -0,0 +1,21 @@
+---
+name: 反馈问题或请求新功能
+about: bug & feature
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+# 尽量每个 issue 只提一个 bug 或新功能
+
+### 提新 issue 前请确认 👉
+
+- 没人提过这个 issue([这里看所有 issue](https://github.com/qier222/YesPlayMusic/issues))
+- 项目的 Todo 里没有与你 issue 相关的内容([这里看 Todo](https://github.com/qier222/YesPlayMusic/projects/1))
+
+### 反馈 bug 需要的信息
+
+- 用的是网页版还是客户端
+- 浏览器名称或电脑操作系统
+- 控制台 Console 页面的截图(按 F12 可打开控制台)
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 77cc6a7..4caf481 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -1,6 +1,15 @@
-name: Build/release
+name: Release
-on: [push, pull_request]
+env:
+ YARN_INSTALL_NOPT: yarn add --ignore-platform --ignore-optional
+
+on:
+ push:
+ branches:
+ - master
+ tags:
+ - v*
+ workflow_dispatch:
jobs:
release:
@@ -8,19 +17,75 @@ jobs:
strategy:
matrix:
- os: [macos-latest, windows-latest]
+ os: [macos-latest, windows-latest, ubuntu-22.04]
steps:
- name: Check out Git repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
+ with:
+ submodules: "recursive"
- name: Install Node.js, NPM and Yarn
- uses: actions/setup-node@v1
+ uses: actions/setup-node@v3
with:
- node-version: 12.16.3
+ node-version: 16
+ cache: 'yarn'
+
+ - 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 libarchive-tools &&
+ 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:
+ snapcraft_token: ${{ secrets.snapcraft_token }}
+
+ - id: get_unm_version
+ name: Get the installed UNM version
+ run: |
+ yarn --ignore-optional
+ unm_version=$(node -e "console.log(require('./node_modules/@unblockneteasemusic/rust-napi/package.json').version)")
+ echo "::set-output name=unmver::${unm_version}"
+ shell: bash
+
+ - name: Install UNM dependencies for Windows
+ if: runner.os == 'Windows'
+ run: |
+ ${{ env.YARN_INSTALL_NOPT }} \
+ @unblockneteasemusic/rust-napi-win32-x64-msvc@${{steps.get_unm_version.outputs.unmver}}
+ shell: bash
+
+ - name: Install UNM dependencies for macOS
+ if: runner.os == 'macOS'
+ run: |
+ ${{ env.YARN_INSTALL_NOPT }} \
+ @unblockneteasemusic/rust-napi-darwin-x64@${{steps.get_unm_version.outputs.unmver}} \
+ @unblockneteasemusic/rust-napi-darwin-arm64@${{steps.get_unm_version.outputs.unmver}} \
+ dmg-license
+ shell: bash
+
+ - name: Install UNM dependencies for Linux
+ if: runner.os == 'Linux'
+ run: |
+ ${{ env.YARN_INSTALL_NOPT }} \
+ @unblockneteasemusic/rust-napi-linux-x64-gnu@${{steps.get_unm_version.outputs.unmver}} \
+ @unblockneteasemusic/rust-napi-linux-arm64-gnu@${{steps.get_unm_version.outputs.unmver}} \
+ @unblockneteasemusic/rust-napi-linux-arm-gnueabihf@${{steps.get_unm_version.outputs.unmver}}
+ shell: bash
- name: Build/release Electron app
- uses: samuelmeuli/action-electron-builder@v1
+ uses: samuelmeuli/action-electron-builder@v1.6.0
+ env:
+ VUE_APP_NETEASE_API_URL: /api
+ VUE_APP_ELECTRON_API_URL: /api
+ VUE_APP_ELECTRON_API_URL_DEV: http://127.0.0.1:10754
+ VUE_APP_LASTFM_API_KEY: 09c55292403d961aa517ff7f5e8a3d9c
+ VUE_APP_LASTFM_API_SHARED_SECRET: 307c9fda32b3904e53654baff215cb67
with:
# GitHub token, automatically provided to the action
# (No need to define this secret in the repo settings)
@@ -32,14 +97,20 @@ jobs:
use_vue_cli: true
- - uses: actions/upload-artifact@v2
+ - uses: actions/upload-artifact@v3
with:
name: YesPlayMusic-mac
- path: dist_electron/*.dmg
+ path: dist_electron/*-universal.dmg
if-no-files-found: ignore
- - uses: actions/upload-artifact@v2
+ - uses: actions/upload-artifact@v3
with:
name: YesPlayMusic-win
- path: dist_electron/*.exe
+ path: dist_electron/*Setup*.exe
+ if-no-files-found: ignore
+
+ - uses: actions/upload-artifact@v3
+ with:
+ name: YesPlayMusic-linux
+ path: dist_electron/*.AppImage
if-no-files-found: ignore
diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml
new file mode 100644
index 0000000..f8e0be8
--- /dev/null
+++ b/.github/workflows/sync.yml
@@ -0,0 +1,48 @@
+name: Upstream Sync
+
+permissions:
+ contents: write
+ issues: write
+ actions: write
+
+on:
+ schedule:
+ - cron: '0 * * * *' # every hour
+ workflow_dispatch:
+
+jobs:
+ sync_latest_from_upstream:
+ name: Sync latest commits from upstream repo
+ runs-on: ubuntu-latest
+ if: ${{ github.event.repository.fork }}
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Clean issue notice
+ uses: actions-cool/issues-helper@v3
+ with:
+ actions: 'close-issues'
+ labels: '🚨 Sync Fail'
+
+ - name: Sync upstream changes
+ id: sync
+ uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
+ with:
+ upstream_sync_repo: qier222/YesPlayMusic
+ upstream_sync_branch: master
+ target_sync_branch: master
+ target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set
+ test_mode: false
+
+ - name: Sync check
+ if: failure()
+ uses: actions-cool/issues-helper@v3
+ with:
+ actions: 'create-issue'
+ title: '🚨 同步失败 | Sync Fail'
+ labels: '🚨 Sync Fail'
+ body: |
+ Due to a change in the workflow file of the upstream repository, GitHub has automatically suspended the scheduled automatic update. You need to manually sync your fork.
+
+ 由于上游仓库的 workflow 文件变更,导致 GitHub 自动暂停了本次自动更新,你需要手动 Sync Fork 一次。
diff --git a/.gitignore b/.gitignore
index e02847c..d8dd8fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,6 @@
.DS_Store
node_modules
/dist
-dist_electron
-Icon?
# local env files
.env
@@ -25,9 +23,21 @@ pnpm-debug.log*
*.sw?
.vercel
-/netease_api
#Electron-builder output
/dist_electron
NeteaseCloudMusicApi-master
NeteaseCloudMusicApi-master.zip
+
+# Local Netlify folder
+.netlify
+vercel.json
+# Devenv
+.devenv*
+devenv.local.nix
+
+# direnv
+.direnv
+
+# pre-commit
+.pre-commit-config.yaml
diff --git a/.npmrc b/.npmrc
deleted file mode 100644
index adcf848..0000000
--- a/.npmrc
+++ /dev/null
@@ -1,4 +0,0 @@
-# 如果发现 npm / yarn 安装太慢,可以解除注释
-# registry=https://registry.npm.taobao.org/
-# ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron
-# phantomjs_cdnurl=https://npm.taobao.org/dist/phantomjs
\ No newline at end of file
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..da2d398
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+14
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
index 15709e8..13859d2 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,5 +1,3 @@
build
coverage
dist
-netease_api
-
diff --git a/.prettierrc b/.prettierrc
index 86f82d1..38800a3 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -3,10 +3,9 @@
"tabWidth": 2,
"useTabs": false,
"semi": true,
- "singleQuote": false,
+ "singleQuote": true,
"jsxSingleQuote": true,
- "jsxBracketSameLine": false,
- "arrowParens": "always",
+ "arrowParens": "avoid",
"endOfLine": "lf",
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "strict"
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..2fb8f95
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,25 @@
+FROM node:16.13.1-alpine as build
+ENV VUE_APP_NETEASE_API_URL=/api
+WORKDIR /app
+RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories &&\
+ apk add --no-cache python3 make g++ git
+COPY package.json yarn.lock ./
+RUN yarn install
+COPY . .
+RUN yarn config set electron_mirror https://npmmirror.com/mirrors/electron/ && \
+ yarn build
+
+FROM nginx:1.20.2-alpine as app
+
+COPY --from=build /app/package.json /usr/local/lib/
+
+RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories &&\
+ apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/v3.14/main libuv \
+ && apk add --no-cache --update-cache --repository http://dl-cdn.alpinelinux.org/alpine/v3.14/main nodejs npm \
+ && npm i -g $(awk -F \" '{if($2=="NeteaseCloudMusicApi") print $2"@"$4}' /usr/local/lib/package.json) \
+ && rm -f /usr/local/lib/package.json
+
+COPY --from=build /app/docker/nginx.conf.example /etc/nginx/conf.d/default.conf
+COPY --from=build /app/dist /usr/share/nginx/html
+
+CMD nginx ; exec npx NeteaseCloudMusicApi
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index d8911ac..7385fd2 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020 qier222
+Copyright (c) 2020-2023 qier222
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
+SOFTWARE.
diff --git a/README.md b/README.md
index ed7ffbc..146f524 100644
--- a/README.md
+++ b/README.md
@@ -8,69 +8,221 @@
高颜值的第三方网易云播放器
- 🌎 访问DEMO |
- 📦️ 下载安装包
+ 🌎 访问DEMO |
+ 📦️ 下载安装包 |
+ 💬 加入交流群
-[![Library][library-screenshot]](https://music.qier222.com)
+[![Library][library-screenshot]](https://music.ineko.cc)
+
+
+## 全新版本
+全新2.0 Alpha测试版已发布,欢迎前往 [Releases](https://github.com/qier222/YesPlayMusic/releases) 页面下载。
+当前版本将会进入维护模式,除重大bug修复外,不会再更新新功能。
## ✨ 特性
- ✅ 使用 Vue.js 全家桶开发
-- 🔴 网易云账号登录
-- 📺 MV 播放
+- 🔴 网易云账号登录(扫码/手机/邮箱登录)
+- 📺 支持 MV 播放
- 📃 支持歌词显示
+- 📻 支持私人 FM / 每日推荐歌曲
- 🚫🤝 无任何社交功能
- 🌎️ 海外用户可直接播放(需要登录网易云账号)
-- 🔐 支持 [UnblockNeteaseMusic](https://github.com/nondanee/UnblockNeteaseMusic),自动使用 QQ/酷狗/酷我音源替换变灰歌曲链接 (网页版不支持)
-- ⏭️ 支持 MediaSession API,可以使用系统快捷键操作上一首下一首
+- 🔐 支持 [UnblockNeteaseMusic](https://github.com/UnblockNeteaseMusic/server#音源清单),自动使用[各类音源](https://github.com/UnblockNeteaseMusic/server#音源清单)替换变灰歌曲链接 (网页版不支持)
+ - 「各类音源」指默认启用的音源。
+ - YouTube 音源需自行安装 `yt-dlp`。
- ✔️ 每日自动签到(手机端和电脑端同时签到)
- 🌚 Light/Dark Mode 自动切换
- 👆 支持 Touch Bar
- 🖥️ 支持 PWA,可在 Chrome/Edge 里点击地址栏右边的 ➕ 安装到电脑
-- 🙉 支持显示歌曲和专辑的 Explicit 标志
+- 🟥 支持 Last.fm Scrobble
+- ☁️ 支持音乐云盘
+- ⌨️ 自定义快捷键和全局快捷键
+- 🎧 支持 Mpris
- 🛠 更多特性开发中
## 📦️ 安装
Electron 版本由 [@hawtim](https://github.com/hawtim) 和 [@qier222](https://github.com/qier222) 适配并维护,支持 macOS、Windows、Linux。
-访问本项目的 [Releases](https://github.com/qier222/YesPlayMusic/releases) 页面下载安装包,或者访问 [镜像下载站 (大陆访问更快)](https://dl.qier222.com/YesPlayMusic/) 下载。
+访问本项目的 [Releases](https://github.com/qier222/YesPlayMusic/releases)
+页面下载安装包。
-## ⚙️ 部署至服务器
+- macOS 用户可以通过 Homebrew 来安装:`brew install --cask yesplaymusic`
-除了下载安装包使用,你还可以将本项目部署到你的服务器上。
+- Windows 用户可以通过 Scoop 来安装:`scoop install extras/yesplaymusic`
+
+## ⚙️ 部署至 Vercel
+
+除了下载安装包使用,你还可以将本项目部署到 Vercel 或你的服务器上。下面是部署到 Vercel 的方法。
+
+本项目的 Demo (https://music.qier222.com) 就是部署在 Vercel 上的网站。
+
+[](https://vercel.com/?utm_source=ohmusic&utm_campaign=oss)
+
+1. 部署网易云 API,详情参见 [Binaryify/NeteaseCloudMusicApi](https://neteasecloudmusicapi.vercel.app/#/?id=%e5%ae%89%e8%a3%85)
+ 。你也可以将 API 部署到 Vercel。
+
+2. 点击本仓库右上角的 Fork,复制本仓库到你的 GitHub 账号。
+
+3. 点击仓库的 Add File,选择 Create new file,输入 `vercel.json`,将下面的内容复制粘贴到文件中,并将 `https://your-netease-api.example.com` 替换为你刚刚部署的网易云 API 地址:
+
+```json
+{
+ "rewrites": [
+ {
+ "source": "/api/:match*",
+ "destination": "https://your-netease-api.example.com/:match*"
+ }
+ ]
+}
+```
+
+4. 打开 [Vercel.com](https://vercel.com),使用 GitHub 登录。
+
+5. 点击 Import Git Repository 并选择你刚刚复制的仓库并点击 Import。
+
+6. 点击 PERSONAL ACCOUNT 旁边的 Select。
+
+7. 点击 Environment Variables,填写 Name 为 `VUE_APP_NETEASE_API_URL`,Value 为 `/api`,点击 Add。最后点击底部的 Deploy 就可以部署到
+ Vercel 了。
+
+## ⚙️ 部署到自己的服务器
+
+除了部署到 Vercel,你还可以部署到自己的服务器上
1. 部署网易云 API,详情参见 [Binaryify/NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)
-
2. 克隆本仓库
```sh
-git clone https://github.com/qier222/YesPlayMusic.git
+git clone --recursive https://github.com/qier222/YesPlayMusic.git
```
3. 安装依赖
```sh
yarn install
+
```
-4. 复制 `/.env.example` 文件为 `/.env`,修改里面 `VUE_APP_NETEASE_API_URL` 的值为网易云 API 地址。本地开发的话可以填写 API 地址为 `http://localhost:3000`,YesPlayMusic 地址为 `http://localhost:8080`
+4. (可选)使用 Nginx 反向代理 API,将 API 路径映射为 `/api`,如果 API 和网页不在同一个域名下的话(跨域),会有一些 bug。
+
+5. 复制 `/.env.example` 文件为 `/.env`,修改里面 `VUE_APP_NETEASE_API_URL` 的值为网易云 API 地址。本地开发的话可以填写 API 地址为 `http://localhost:3000`,YesPlayMusic 地址为 `http://localhost:8080`。如果你使用了反向代理 API,可以填写 API 地址为 `/api`。
```
VUE_APP_NETEASE_API_URL=http://localhost:3000
```
-5. 编译打包
+6. 编译打包
```sh
yarn run build
```
-6. 将 `/dist` 目录下的文件上传到你的 Web 服务器
+7. 将 `/dist` 目录下的文件上传到你的 Web 服务器
+
+## ⚙️ 宝塔面板 docker应用商店 部署
+
+1. 安装宝塔面板,前往[宝塔面板官网](https://www.bt.cn/new/download.html) ,选择正式版的脚本下载安装。
+
+2. 安装后登录宝塔面板,在左侧导航栏中点击 Docker,首次进入会提示安装Docker服务,点击立即安装,按提示完成安装
+
+3. 安装完成后在应用商店中找到YesPlayMusic,点击安装,配置域名、端口等基本信息即可完成安装。
+
+4. 安装后在浏览器输入上一步骤设置的域名即可访问。
+
+## ⚙️ Docker 部署
+
+1. 构建 Docker Image
+
+```sh
+docker build -t yesplaymusic .
+```
+
+2. 启动 Docker Container
+
+```sh
+docker run -d --name YesPlayMusic -p 80:80 yesplaymusic
+```
+
+3. Docker Compose 启动
+
+```sh
+docker-compose up -d
+```
+
+YesPlayMusic 地址为 `http://localhost`
+
+## ⚙️ 部署至 Replit
+
+1. 新建 Repl,选择 Bash 模板
+
+2. 在 Replit shell 中运行以下命令
+
+```sh
+bash <(curl -s -L https://raw.githubusercontent.com/qier222/YesPlayMusic/main/install-replit.sh)
+```
+
+3. 首次运行成功后,只需点击绿色按钮 `Run` 即可再次运行
+
+4. 由于 replit 个人版限制内存为 1G(教育版为 3G),构建过程中可能会失败,请再次运行上述命令或运行以下命令:
+
+```sh
+cd /home/runner/${REPL_SLUG}/music && yarn install && yarn run build
+```
+
+## 👷♂️ 打包客户端
+
+如果在 Release 页面没有找到适合你的设备的安装包的话,你可以根据下面的步骤来打包自己的客户端。
+
+1. 打包 Electron 需要用到 Node.js 和 Yarn。可前往 [Node.js 官网](https://nodejs.org/zh-cn/) 下载安装包。安装 Node.js
+ 后可在终端里执行 `npm install -g yarn` 来安装 Yarn。
+
+2. 使用 `git clone --recursive https://github.com/qier222/YesPlayMusic.git` 克隆本仓库到本地。
+
+3. 使用 `yarn install` 安装项目依赖。
+
+4. 复制 `/.env.example` 文件为 `/.env` 。
+
+5. 选择下列表格的命令来打包适合的你的安装包,打包出来的文件在 `/dist_electron` 目录下。了解更多信息可访问 [electron-builder 文档](https://www.electron.build/cli)
+
+| 命令 | 说明 |
+| ------------------------------------------ | ------------------------- |
+| `yarn electron:build --windows nsis:ia32` | Windows 32 位 |
+| `yarn electron:build --windows nsis:arm64` | Windows ARM |
+| `yarn electron:build --linux deb:armv7l` | Debian armv7l(树莓派等) |
+| `yarn electron:build --macos dir:arm64` | macOS ARM |
+
+## :computer: 配置开发环境
+
+本项目由 [NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi) 提供 API。
+
+运行本项目
+
+```shell
+# 安装依赖
+yarn install
+
+# 创建本地环境变量
+cp .env.example .env
+
+# 运行(网页端)
+yarn serve
+
+# 运行(electron)
+yarn electron:serve
+```
+
+本地运行 NeteaseCloudMusicApi,或者将 API [部署至 Vercel](#%EF%B8%8F-部署至-vercel)
+
+```shell
+# 运行 API (默认 3000 端口)
+yarn netease_api:run
+```
## ☑️ Todo
@@ -84,14 +236,25 @@ yarn run build
基于 [MIT license](https://opensource.org/licenses/MIT) 许可进行开源。
+## 灵感来源
+
+API 源代码来自 [Binaryify/NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)
+
+- [Apple Music](https://music.apple.com)
+- [YouTube Music](https://music.youtube.com)
+- [Spotify](https://www.spotify.com)
+- [网易云音乐](https://music.163.com)
+
## 🖼️ 截图
-[![artist][artist-screenshot]](https://music.qier222.com)
-[![album][album-screenshot]](https://music.qier222.com)
-[![playlist][playlist-screenshot]](https://music.qier222.com)
-[![explore][explore-screenshot]](https://music.qier222.com)
-[![search][search-screenshot]](https://music.qier222.com)
-[![home][home-screenshot]](https://music.qier222.com)
+![lyrics][lyrics-screenshot]
+![library-dark][library-dark-screenshot]
+![album][album-screenshot]
+![home-2][home-2-screenshot]
+![artist][artist-screenshot]
+![search][search-screenshot]
+![home][home-screenshot]
+![explore][explore-screenshot]
@@ -100,6 +263,8 @@ yarn run build
[artist-screenshot]: images/artist.png
[explore-screenshot]: images/explore.png
[home-screenshot]: images/home.png
+[home-2-screenshot]: images/home-2.png
+[lyrics-screenshot]: images/lyrics.png
[library-screenshot]: images/library.png
-[playlist-screenshot]: images/playlist.png
+[library-dark-screenshot]: images/library-dark.png
[search-screenshot]: images/search.png
diff --git a/babel.config.js b/babel.config.js
index c0fa85e..707ce4a 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -1,7 +1,11 @@
module.exports = {
- presets: ["@vue/cli-plugin-babel/preset"],
- plugins: [
- "@babel/plugin-proposal-nullish-coalescing-operator",
- "@babel/plugin-proposal-optional-chaining",
+ presets: [
+ [
+ '@vue/cli-plugin-babel/preset',
+ {
+ useBuiltIns: 'usage',
+ shippedProposals: true,
+ },
+ ],
],
};
diff --git a/devenv.lock b/devenv.lock
new file mode 100644
index 0000000..9250350
--- /dev/null
+++ b/devenv.lock
@@ -0,0 +1,132 @@
+{
+ "nodes": {
+ "devenv": {
+ "locked": {
+ "dir": "src/modules",
+ "lastModified": 1730412360,
+ "owner": "cachix",
+ "repo": "devenv",
+ "rev": "45847cb1f14a6d8cfa86ea943703c54a8798ae7e",
+ "type": "github"
+ },
+ "original": {
+ "dir": "src/modules",
+ "owner": "cachix",
+ "repo": "devenv",
+ "type": "github"
+ }
+ },
+ "flake-compat": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1696426674,
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "gitignore": {
+ "inputs": {
+ "nixpkgs": [
+ "pre-commit-hooks",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1709087332,
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1730272153,
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "2d2a9ddbe3f2c00747398f3dc9b05f7f2ebb0f53",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs-stable": {
+ "locked": {
+ "lastModified": 1730327045,
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "080166c15633801df010977d9d7474b4a6c549d7",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-24.05",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nodejs16": {
+ "locked": {
+ "lastModified": 1700230496,
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "a71323f68d4377d12c04a5410e214495ec598d4c",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "a71323f68d4377d12c04a5410e214495ec598d4c",
+ "type": "github"
+ }
+ },
+ "pre-commit-hooks": {
+ "inputs": {
+ "flake-compat": "flake-compat",
+ "gitignore": "gitignore",
+ "nixpkgs": [
+ "nixpkgs"
+ ],
+ "nixpkgs-stable": "nixpkgs-stable"
+ },
+ "locked": {
+ "lastModified": 1730302582,
+ "owner": "cachix",
+ "repo": "pre-commit-hooks.nix",
+ "rev": "af8a16fe5c264f5e9e18bcee2859b40a656876cf",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "repo": "pre-commit-hooks.nix",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "devenv": "devenv",
+ "nixpkgs": "nixpkgs",
+ "nodejs16": "nodejs16",
+ "pre-commit-hooks": "pre-commit-hooks"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/devenv.nix b/devenv.nix
new file mode 100644
index 0000000..fd6b9ce
--- /dev/null
+++ b/devenv.nix
@@ -0,0 +1,53 @@
+{ pkgs, lib, config, inputs, ... }:
+
+let
+ nodejs16 = import inputs.nodejs16 { system = pkgs.stdenv.system; };
+in
+{
+ # https://devenv.sh/basics/
+ env.GREET = "devenv";
+
+ # https://devenv.sh/packages/
+ packages = [ pkgs.git ] ++ lib.optionals pkgs.stdenv.isDarwin (with pkgs.darwin.apple_sdk; [
+ frameworks.AppKit
+ ]);
+
+ # https://devenv.sh/languages/
+ languages.javascript.enable = true;
+ languages.javascript.package = nodejs16.pkgs.nodejs_16;
+ languages.javascript.corepack.enable = true;
+ # languages.rust.enable = true;
+
+ # https://devenv.sh/processes/
+ # processes.cargo-watch.exec = "cargo-watch";
+
+ # https://devenv.sh/services/
+ # services.postgres.enable = true;
+
+ # https://devenv.sh/scripts/
+ scripts.hello.exec = ''
+ echo hello from $GREET
+ '';
+
+ enterShell = ''
+ hello
+ git --version
+ '';
+
+ # https://devenv.sh/tasks/
+ # tasks = {
+ # "myproj:setup".exec = "mytool build";
+ # "devenv:enterShell".after = [ "myproj:setup" ];
+ # };
+
+ # https://devenv.sh/tests/
+ enterTest = ''
+ echo "Running tests"
+ git --version | grep --color=auto "${pkgs.git.version}"
+ '';
+
+ # https://devenv.sh/pre-commit-hooks/
+ # pre-commit.hooks.shellcheck.enable = true;
+
+ # See full reference at https://devenv.sh/reference/options/
+}
diff --git a/devenv.yaml b/devenv.yaml
new file mode 100644
index 0000000..7019c5c
--- /dev/null
+++ b/devenv.yaml
@@ -0,0 +1,19 @@
+# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
+inputs:
+ nixpkgs:
+ url: github:nixos/nixpkgs/nixpkgs-unstable
+ nodejs16:
+ url: github:nixos/nixpkgs/a71323f68d4377d12c04a5410e214495ec598d4c
+
+# https://github.com/cachix/devenv/issues/792#issuecomment-2043166453
+impure: true
+# If you're using non-OSS software, you can set allowUnfree to true.
+# allowUnfree: true
+
+# If you're willing to use a package that's vulnerable
+# permittedInsecurePackages:
+# - "openssl-1.1.1w"
+
+# If you have more than one devenv you can merge them
+#imports:
+# - ./backend
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..0930218
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,39 @@
+services:
+ YesPlayMusic:
+ build:
+ context: .
+ image: yesplaymusic
+ container_name: YesPlayMusic
+ volumes:
+ - /etc/localtime:/etc/localtime:ro
+ - /etc/timezone:/etc/timezone:ro
+ - ./docker/nginx.conf.example:/etc/nginx/conf.d/default.conf:ro
+ ports:
+ - 80:80
+ restart: always
+ depends_on:
+ - UnblockNeteaseMusic
+ environment:
+ - NODE_TLS_REJECT_UNAUTHORIZED=0
+ networks:
+ my_network:
+
+ UnblockNeteaseMusic:
+ image: pan93412/unblock-netease-music-enhanced
+ command: -o kugou kuwo migu bilibili pyncmd -p 80:443 -f 45.127.129.53 -e -
+ # environment:
+ # JSON_LOG: true
+ # LOG_LEVEL: debug
+ networks:
+ my_network:
+ aliases:
+ - music.163.com
+ - interface.music.163.com
+ - interface3.music.163.com
+ - interface.music.163.com.163jiasu.com
+ - interface3.music.163.com.163jiasu.com
+ restart: always
+
+networks:
+ my_network:
+ driver: bridge
diff --git a/docker/nginx.conf.example b/docker/nginx.conf.example
new file mode 100644
index 0000000..cdab218
--- /dev/null
+++ b/docker/nginx.conf.example
@@ -0,0 +1,28 @@
+server {
+ gzip on;
+ listen 80;
+ listen [::]:80;
+ 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/;
+ }
+}
diff --git a/images/album.png b/images/album.png
index 66a7715..bf5936c 100644
Binary files a/images/album.png and b/images/album.png differ
diff --git a/images/artist.png b/images/artist.png
index d02c91a..7b5ac77 100644
Binary files a/images/artist.png and b/images/artist.png differ
diff --git a/images/explore.png b/images/explore.png
index bcf4f76..7fd0cfc 100644
Binary files a/images/explore.png and b/images/explore.png differ
diff --git a/images/home-2.png b/images/home-2.png
new file mode 100644
index 0000000..0b98d7d
Binary files /dev/null and b/images/home-2.png differ
diff --git a/images/home.png b/images/home.png
index 3be5d9f..6de45c6 100644
Binary files a/images/home.png and b/images/home.png differ
diff --git a/images/library-dark.png b/images/library-dark.png
new file mode 100644
index 0000000..ca9f67a
Binary files /dev/null and b/images/library-dark.png differ
diff --git a/images/library.png b/images/library.png
index d6bef0f..bd28ce0 100644
Binary files a/images/library.png and b/images/library.png differ
diff --git a/images/lyrics.png b/images/lyrics.png
new file mode 100644
index 0000000..522747a
Binary files /dev/null and b/images/lyrics.png differ
diff --git a/images/playlist.png b/images/playlist.png
deleted file mode 100644
index 90aa61d..0000000
Binary files a/images/playlist.png and /dev/null differ
diff --git a/images/search.png b/images/search.png
index fd4320a..2befdfc 100644
Binary files a/images/search.png and b/images/search.png differ
diff --git a/install-replit.sh b/install-replit.sh
new file mode 100644
index 0000000..c17e438
--- /dev/null
+++ b/install-replit.sh
@@ -0,0 +1,28 @@
+ #!/usr/bin/bash
+
+# 初始化 .replit 和 replit.nix
+if [[ $1 == i ]];then
+ echo -e 'run = ["bash", "main.sh"]\n\nentrypoint = "main.sh"' >.replit
+ echo -e "{ pkgs }: {\n\t\tdeps = [\n\t\t\tpkgs.nodejs-16_x\n\t\t\tpkgs.yarn\n\t\t\tpkgs.bashInteractive\n\t\t];\n}" > replit.nix
+ exit
+fi
+
+# 安装
+if [[ ! -d api ]];then
+ mkdir api
+ git clone https://github.com/Binaryify/NeteaseCloudMusicApi ./api && \
+ cd api && npm install && cd ..
+fi
+
+if [[ ! -d music ]];then
+ mkdir music
+ git clone https://github.com/qier222/YesPlayMusic ./music && \
+ cd music && cp .env.example .env && npm install --force && npm run build && cd ..
+fi
+
+# 启动
+PID=`ps -ef | grep npm | awk '{print $2}' | sed '$d'`
+
+if [[ ! -z ${PID} ]];then echo $PID | xargs kill;fi
+nohup bash -c 'cd api && PORT=35216 node app.js' > api.log 2>&1
+nohup bash -c 'npx serve music/dist/' > music.log 2>&1
diff --git a/jsconfig.json b/jsconfig.json
index 0d612a8..7787662 100644
--- a/jsconfig.json
+++ b/jsconfig.json
@@ -7,7 +7,8 @@
},
"target": "ES6",
"module": "commonjs",
- "allowSyntheticDefaultImports": true
+ "allowSyntheticDefaultImports": true,
+ "jsx": "preserve"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
diff --git a/package.json b/package.json
index 2192524..bb87fdd 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,9 @@
{
- "name": "YesPlayMusic",
- "version": "0.3.2",
+ "name": "yesplaymusic",
+ "version": "0.4.9",
"private": true,
- "description": "A third party music application for Netease Music",
- "author": "hawtim",
+ "description": "A third party music player for Netease Music",
+ "author": "qier222",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
@@ -18,56 +18,68 @@
"electron:publish": "vue-cli-service electron:build -mwl -p always",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps",
- "prettier": "npx prettier --write ./src ./script",
- "netease_api:run": "cd ./netease_api && npm run start",
- "netease_api:pull": "node script/pull.js",
- "netease_api:install": "cd ./netease_api && npm install",
- "netease_api:setup": "npm run netease_api:pull && npm run netease_api:install"
+ "prettier": "npx prettier --write ./src",
+ "netease_api:run": "npx NeteaseCloudMusicApi"
},
"main": "background.js",
+ "engines": {
+ "node": "14 || 16"
+ },
"dependencies": {
- "@njzy/unblockneteasemusic": "^0.25.3",
- "axios": "^0.21.0",
- "big-integer": "^1.6.48",
+ "@unblockneteasemusic/rust-napi": "^0.4.0",
+ "NeteaseCloudMusicApi": "^4.23.3",
+ "axios": "^0.26.1",
+ "change-case": "^4.1.2",
+ "cli-color": "^2.0.0",
+ "color": "^4.2.3",
"core-js": "^3.6.5",
"crypto-js": "^4.0.0",
"dayjs": "^1.8.36",
- "electron": "11.0.2",
- "electron-context-menu": "^2.3.0",
+ "dexie": "^3.0.3",
+ "discord-rich-presence": "^0.0.8",
+ "electron": "^13.6.7",
+ "electron-builder": "^23.0.0",
+ "electron-context-menu": "^3.1.2",
"electron-debug": "^3.1.0",
- "electron-devtools-installer": "^3.1.1",
- "electron-icon-builder": "^1.0.2",
- "electron-is-dev": "^1.2.0",
+ "electron-devtools-installer": "^3.2",
+ "electron-icon-builder": "^2.0.1",
+ "electron-is-dev": "^2.0.0",
"electron-log": "^4.3.0",
- "electron-store": "^6.0.1",
- "electron-updater": "^4.3.5",
+ "electron-store": "^8.0.1",
+ "electron-updater": "^5.0.1",
+ "esbuild": "^0.20.1",
+ "esbuild-loader": "^4.0.3",
"express": "^4.17.1",
"express-fileupload": "^1.2.0",
"express-http-proxy": "^1.6.2",
"extract-zip": "^2.0.1",
- "howler": "^2.2.0",
+ "howler": "^2.2.3",
"js-cookie": "^2.2.1",
- "localforage": "^1.9.0",
+ "jsbi": "^4.1.0",
"lodash": "^4.17.20",
+ "md5": "^2.3.0",
+ "mpris-service": "^2.1.2",
+ "music-metadata": "^7.5.3",
+ "node-vibrant": "^3.2.1-alpha.1",
"nprogress": "^0.2.0",
"pac-proxy-agent": "^4.1.0",
"plyr": "^3.6.2",
- "prettier": "2.1.2",
+ "qrcode": "^1.4.4",
"register-service-worker": "^1.7.1",
- "svg-sprite-loader": "^5.0.0",
+ "svg-sprite-loader": "^6.0.11",
"tunnel": "^0.0.6",
+ "vscode-codicons": "^0.0.17",
"vue": "^2.6.11",
- "vue-analytics": "^5.22.1",
- "vue-electron": "^1.0.6",
+ "vue-clipboard2": "^0.3.1",
+ "vue-gtag": "1",
"vue-i18n": "^8.22.0",
"vue-router": "^3.4.3",
"vue-slider-component": "^3.2.5",
- "vuex": "^3.4.0"
+ "vuex": "^3.4.0",
+ "x11": "^2.3.0"
},
"devDependencies": {
- "@sentry/browser": "^5.27.0",
- "@sentry/integrations": "^5.27.0",
- "@sentry/tracing": "^5.27.0",
+ "@types/node": "^17.0.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-pwa": "~4.5.0",
@@ -75,13 +87,21 @@
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
- "eslint-plugin-vue": "^6.2.2",
+ "eslint-config-prettier": "^8.1.0",
+ "eslint-plugin-prettier": "^3.3.1",
+ "eslint-plugin-vue": "^7.9.0",
"husky": "^4.3.0",
+ "prettier": "2.5.1",
"sass": "^1.26.11",
"sass-loader": "^10.0.2",
- "vue-cli-plugin-electron-builder": "~2.0.0-rc.4",
+ "vue-cli-plugin-electron-builder": "~2.1.1",
"vue-template-compiler": "^2.6.11"
},
+ "resolutions": {
+ "icon-gen": "3.0.0",
+ "degenerator": "2.2.0",
+ "electron-builder": "^23.0.0"
+ },
"eslintConfig": {
"root": true,
"env": {
@@ -90,6 +110,8 @@
},
"extends": [
"plugin:vue/essential",
+ "plugin:vue/recommended",
+ "plugin:prettier/recommended",
"eslint:recommended"
],
"parserOptions": {
diff --git a/public/img/icons/exit.png b/public/img/icons/exit.png
new file mode 100644
index 0000000..01e21b2
Binary files /dev/null and b/public/img/icons/exit.png differ
diff --git a/public/img/icons/left.png b/public/img/icons/left.png
new file mode 100644
index 0000000..9e49d7e
Binary files /dev/null and b/public/img/icons/left.png differ
diff --git a/public/img/icons/like.png b/public/img/icons/like.png
new file mode 100644
index 0000000..4bea102
Binary files /dev/null and b/public/img/icons/like.png differ
diff --git a/public/img/icons/menu-dark@88.png b/public/img/icons/menu-dark@88.png
new file mode 100644
index 0000000..a2feb00
Binary files /dev/null and b/public/img/icons/menu-dark@88.png differ
diff --git a/public/img/icons/menu@88.png b/public/img/icons/menu-light@88.png
similarity index 100%
rename from public/img/icons/menu@88.png
rename to public/img/icons/menu-light@88.png
diff --git a/public/img/icons/pause.png b/public/img/icons/pause.png
new file mode 100644
index 0000000..509d738
Binary files /dev/null and b/public/img/icons/pause.png differ
diff --git a/public/img/icons/play.png b/public/img/icons/play.png
new file mode 100644
index 0000000..90537c8
Binary files /dev/null and b/public/img/icons/play.png differ
diff --git a/public/img/icons/repeat.png b/public/img/icons/repeat.png
new file mode 100644
index 0000000..d4c3fc7
Binary files /dev/null and b/public/img/icons/repeat.png differ
diff --git a/public/img/icons/right.png b/public/img/icons/right.png
new file mode 100644
index 0000000..50c2e75
Binary files /dev/null and b/public/img/icons/right.png differ
diff --git a/public/img/icons/unlike.png b/public/img/icons/unlike.png
new file mode 100644
index 0000000..a0afa24
Binary files /dev/null and b/public/img/icons/unlike.png differ
diff --git a/public/img/logos/lastfm.png b/public/img/logos/lastfm.png
new file mode 100644
index 0000000..e3964f6
Binary files /dev/null and b/public/img/logos/lastfm.png differ
diff --git a/public/img/logos/nyancat-stop.png b/public/img/logos/nyancat-stop.png
new file mode 100644
index 0000000..72bf07e
Binary files /dev/null and b/public/img/logos/nyancat-stop.png differ
diff --git a/public/img/logos/yesplaymusic-white24x24.png b/public/img/logos/yesplaymusic-white24x24.png
new file mode 100644
index 0000000..d8e4715
Binary files /dev/null and b/public/img/logos/yesplaymusic-white24x24.png differ
diff --git a/public/img/touchbar/backward.png b/public/img/touchbar/backward.png
new file mode 100644
index 0000000..83b2053
Binary files /dev/null and b/public/img/touchbar/backward.png differ
diff --git a/public/img/touchbar/forward.png b/public/img/touchbar/forward.png
new file mode 100644
index 0000000..5577bb5
Binary files /dev/null and b/public/img/touchbar/forward.png differ
diff --git a/public/img/touchbar/like.png b/public/img/touchbar/like.png
new file mode 100644
index 0000000..09f6534
Binary files /dev/null and b/public/img/touchbar/like.png differ
diff --git a/public/img/touchbar/like_fill.png b/public/img/touchbar/like_fill.png
new file mode 100644
index 0000000..bbc2727
Binary files /dev/null and b/public/img/touchbar/like_fill.png differ
diff --git a/public/img/touchbar/next_up.png b/public/img/touchbar/next_up.png
new file mode 100644
index 0000000..60d67a0
Binary files /dev/null and b/public/img/touchbar/next_up.png differ
diff --git a/public/img/touchbar/page_next.png b/public/img/touchbar/page_next.png
new file mode 100644
index 0000000..24ab389
Binary files /dev/null and b/public/img/touchbar/page_next.png differ
diff --git a/public/img/touchbar/page_prev.png b/public/img/touchbar/page_prev.png
new file mode 100644
index 0000000..52cb588
Binary files /dev/null and b/public/img/touchbar/page_prev.png differ
diff --git a/public/img/touchbar/pause.png b/public/img/touchbar/pause.png
new file mode 100644
index 0000000..083f680
Binary files /dev/null and b/public/img/touchbar/pause.png differ
diff --git a/public/img/touchbar/play.png b/public/img/touchbar/play.png
new file mode 100644
index 0000000..101b92f
Binary files /dev/null and b/public/img/touchbar/play.png differ
diff --git a/public/img/touchbar/search.png b/public/img/touchbar/search.png
new file mode 100644
index 0000000..e62e725
Binary files /dev/null and b/public/img/touchbar/search.png differ
diff --git a/public/index.html b/public/index.html
index 193d42c..ca6d1ad 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,23 +1,25 @@
-
-
-
-
-
-
- <%= htmlWebpackPlugin.options.title %>
-
-
-
+
+
+
+
diff --git a/public/robots.txt b/public/robots.txt
index eb05362..1f53798 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -1,2 +1,2 @@
User-agent: *
-Disallow:
+Disallow: /
diff --git a/restyled.yml b/restyled.yml
new file mode 100644
index 0000000..c31d3e8
--- /dev/null
+++ b/restyled.yml
@@ -0,0 +1,7 @@
+commit_template: 'style: with ${restyler.name}'
+restylers:
+ - prettier
+ - prettier-json
+ - prettier-markdown
+ - prettier-yaml
+ - whitespace
diff --git a/script/pull.js b/script/pull.js
deleted file mode 100644
index 8f75eb8..0000000
--- a/script/pull.js
+++ /dev/null
@@ -1,103 +0,0 @@
-// node module
-const fs = require("fs");
-const https = require("https");
-const resolve = require("path").resolve;
-const join = require("path").resolve;
-const extract = require("extract-zip");
-
-// 函数参数
-const dest = resolve(__dirname, "../");
-const fileName = "NeteaseCloudMusicApi-master.zip";
-const options = {
- hostname: "github.91chifun.workers.dev",
- path: `//https://github.com/Binaryify/NeteaseCloudMusicApi/archive/master.zip`,
-};
-
-// 完整的流程控制
-/**
- * 1. 检查本地文件是否已有
- * 2. 下载默认/指定版本的 zip 压缩包,等待下载
- * 3. 解压缩
- * 4. 进入目录安装依赖 npm install
- */
-
-function fix2(number) {
- return number.toFixed(2);
-}
-
-async function download(options, fileName, callback) {
- return await new Promise((resolve, reject) => {
- const destPath = join(__dirname, "../" + fileName);
- // Check if exist
- if (fs.existsSync(destPath)) return resolve(destPath);
-
- const file = fs.createWriteStream(destPath);
- const request = https.get(options, (res) => {
- let len = res.headers && parseInt(res.headers["content-length"], 10);
- let cur = 0;
- // 1048576 - bytes in 1Megabyte
- const MEGA = 1048576;
- let total = 0;
- if (len) {
- total = len / MEGA;
- }
- if (!len) {
- console.log(
- "Downloading, but can not get content-length, please be patient."
- );
- }
- res.on("data", (chunk) => {
- if (len) {
- cur += chunk.length;
- console.log(
- `Downloading ${fix2((100.0 * cur) / len)}% ${fix2(
- cur / MEGA
- )}/${fix2(total)}mb`
- );
- }
- });
- res.on("end", () => {
- callback("Downloading complete!");
- });
- res.pipe(file);
- file.on("finish", () => {
- file.close(() => {
- callback("File wrote complete!");
- resolve(destPath);
- });
- });
- file.on("error", (err) => {
- fs.unlink(destPath);
- reject(err);
- });
- request.on("error", (err) => {
- console.log("Error: " + err.message);
- });
- });
- });
-}
-
-async function unzip(source, target) {
- try {
- await extract(source, {
- dir: target,
- });
- console.log("Extraction complete");
- return true;
- } catch (err) {
- // handle any errors
- if (err.message === "end of central directory record signature not found") {
- console.log("Not a full_downloaded zip file, removed!");
- fs.unlinkSync(source);
- }
- return false;
- }
-}
-// Download process
-download(options, fileName, (text) => {
- console.log(text);
-}).then((path) => {
- console.log(path);
- // Unzip process
- return unzip(path, dest);
-});
diff --git a/src/App.vue b/src/App.vue
index 4063e21..59392be 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,39 +1,43 @@
-
-
-
+
diff --git a/src/api/album.js b/src/api/album.js
index ef39d14..18425d4 100644
--- a/src/api/album.js
+++ b/src/api/album.js
@@ -1,5 +1,6 @@
-import request from "@/utils/request";
-import { mapTrackPlayableStatus } from "@/utils/common";
+import request from '@/utils/request';
+import { mapTrackPlayableStatus } from '@/utils/common';
+import { cacheAlbum, getAlbumFromCache } from '@/utils/db';
/**
* 获取专辑内容
@@ -7,15 +8,23 @@ import { mapTrackPlayableStatus } from "@/utils/common";
* @param {number} id
*/
export function getAlbum(id) {
- return request({
- url: "/album",
- method: "get",
- params: {
- id,
- },
- }).then((data) => {
- data.songs = mapTrackPlayableStatus(data.songs);
- return data;
+ const fetchLatest = () => {
+ return request({
+ url: '/album',
+ method: 'get',
+ params: {
+ id,
+ },
+ }).then(data => {
+ cacheAlbum(id, data);
+ data.songs = mapTrackPlayableStatus(data.songs);
+ return data;
+ });
+ };
+ fetchLatest();
+
+ return getAlbumFromCache(id).then(result => {
+ return result ?? fetchLatest();
});
}
@@ -32,8 +41,8 @@ export function getAlbum(id) {
*/
export function newAlbums(params) {
return request({
- url: "/album/new",
- method: "get",
+ url: '/album/new',
+ method: 'get',
params,
});
}
@@ -46,8 +55,8 @@ export function newAlbums(params) {
*/
export function albumDynamicDetail(id) {
return request({
- url: "/album/detail/dynamic",
- method: "get",
+ url: '/album/detail/dynamic',
+ method: 'get',
params: { id, timestamp: new Date().getTime() },
});
}
@@ -63,8 +72,8 @@ export function albumDynamicDetail(id) {
*/
export function likeAAlbum(params) {
return request({
- url: "/album/sub",
- method: "post",
+ url: '/album/sub',
+ method: 'post',
params,
});
}
diff --git a/src/api/artist.js b/src/api/artist.js
index 31b0742..925df45 100644
--- a/src/api/artist.js
+++ b/src/api/artist.js
@@ -1,5 +1,7 @@
-import request from "@/utils/request";
-import { mapTrackPlayableStatus } from "@/utils/common";
+import request from '@/utils/request';
+import { mapTrackPlayableStatus } from '@/utils/common';
+import { isAccountLoggedIn } from '@/utils/auth';
+import { getTrackDetail } from '@/api/track';
/**
* 获取歌手单曲
@@ -8,13 +10,19 @@ import { mapTrackPlayableStatus } from "@/utils/common";
*/
export function getArtist(id) {
return request({
- url: "/artists",
- method: "get",
+ url: '/artists',
+ method: 'get',
params: {
id,
timestamp: new Date().getTime(),
},
- }).then((data) => {
+ }).then(async data => {
+ if (!isAccountLoggedIn()) {
+ const trackIDs = data.hotSongs.map(t => t.id);
+ const tracks = await getTrackDetail(trackIDs.join(','));
+ data.hotSongs = tracks.songs;
+ return data;
+ }
data.hotSongs = mapTrackPlayableStatus(data.hotSongs);
return data;
});
@@ -33,8 +41,8 @@ export function getArtist(id) {
*/
export function getArtistAlbum(params) {
return request({
- url: "/artist/album",
- method: "get",
+ url: '/artist/album',
+ method: 'get',
params,
});
}
@@ -50,12 +58,14 @@ export function getArtistAlbum(params) {
* @param {number=} type
*/
export function toplistOfArtists(type = null) {
+ let params = {};
+ if (type) {
+ params.type = type;
+ }
return request({
- url: "/toplist/artist",
- method: "get",
- params: {
- type,
- },
+ url: '/toplist/artist',
+ method: 'get',
+ params,
});
}
/**
@@ -67,8 +77,8 @@ export function toplistOfArtists(type = null) {
*/
export function artistMv(params) {
return request({
- url: "/artist/mv",
- method: "get",
+ url: '/artist/mv',
+ method: 'get',
params,
});
}
@@ -84,8 +94,8 @@ export function artistMv(params) {
*/
export function followAArtist(params) {
return request({
- url: "/artist/sub",
- method: "post",
+ url: '/artist/sub',
+ method: 'post',
params,
});
}
@@ -98,8 +108,8 @@ export function followAArtist(params) {
*/
export function similarArtists(id) {
return request({
- url: "/simi/artist",
- method: "post",
+ url: '/simi/artist',
+ method: 'post',
params: { id },
});
}
diff --git a/src/api/auth.js b/src/api/auth.js
index 461ac01..294f551 100644
--- a/src/api/auth.js
+++ b/src/api/auth.js
@@ -1,4 +1,4 @@
-import request from "@/utils/request";
+import request from '@/utils/request';
/**
* 手机登录
@@ -14,11 +14,12 @@ import request from "@/utils/request";
*/
export function loginWithPhone(params) {
return request({
- url: "/login/cellphone",
- method: "post",
+ url: '/login/cellphone',
+ method: 'post',
params,
});
}
+
/**
* 邮箱登录
* - email: 163 网易邮箱
@@ -31,12 +32,60 @@ export function loginWithPhone(params) {
*/
export function loginWithEmail(params) {
return request({
- url: "/login",
- method: "post",
+ url: '/login',
+ method: 'post',
params,
});
}
+/**
+ * 二维码key生成接口
+ */
+export function loginQrCodeKey() {
+ return request({
+ url: '/login/qr/key',
+ method: 'get',
+ params: {
+ timestamp: new Date().getTime(),
+ },
+ });
+}
+
+/**
+ * 二维码生成接口
+ * 说明: 调用此接口传入上一个接口生成的key可生成二维码图片的base64和二维码信息,
+ * 可使用base64展示图片,或者使用二维码信息内容自行使用第三方二维码生产库渲染二维码
+ * @param {Object} params
+ * @param {string} params.key
+ * @param {string=} params.qrimg 传入后会额外返回二维码图片base64编码
+ */
+export function loginQrCodeCreate(params) {
+ return request({
+ url: '/login/qr/create',
+ method: 'get',
+ params: {
+ ...params,
+ timestamp: new Date().getTime(),
+ },
+ });
+}
+
+/**
+ * 二维码检测扫码状态接口
+ * 说明: 轮询此接口可获取二维码扫码状态,800为二维码过期,801为等待扫码,802为待确认,803为授权登录成功(803状态码下会返回cookies)
+ * @param {string} key
+ */
+export function loginQrCodeCheck(key) {
+ return request({
+ url: '/login/qr/check',
+ method: 'get',
+ params: {
+ key,
+ timestamp: new Date().getTime(),
+ },
+ });
+}
+
/**
* 刷新登录
* 说明 : 调用此接口 , 可刷新登录状态
@@ -44,8 +93,8 @@ export function loginWithEmail(params) {
*/
export function refreshCookie() {
return request({
- url: "/login/refresh",
- method: "post",
+ url: '/login/refresh',
+ method: 'post',
});
}
@@ -55,7 +104,7 @@ export function refreshCookie() {
*/
export function logout() {
return request({
- url: "/logout",
- method: "post",
+ url: '/logout',
+ method: 'post',
});
}
diff --git a/src/api/lastfm.js b/src/api/lastfm.js
new file mode 100644
index 0000000..41ad9c5
--- /dev/null
+++ b/src/api/lastfm.js
@@ -0,0 +1,80 @@
+// Last.fm API documents 👉 https://www.last.fm/api
+
+import axios from 'axios';
+import md5 from 'crypto-js/md5';
+
+const apiKey = process.env.VUE_APP_LASTFM_API_KEY;
+const apiSharedSecret = process.env.VUE_APP_LASTFM_API_SHARED_SECRET;
+const baseUrl = window.location.origin;
+const url = 'https://ws.audioscrobbler.com/2.0/';
+
+const sign = params => {
+ const sortParamsKeys = Object.keys(params).sort();
+ const sortedParams = sortParamsKeys.reduce((acc, key) => {
+ acc[key] = params[key];
+ return acc;
+ }, {});
+ let signature = '';
+ for (const [key, value] of Object.entries(sortedParams)) {
+ signature += `${key}${value}`;
+ }
+ return md5(signature + apiSharedSecret).toString();
+};
+
+export function auth() {
+ const url = process.env.IS_ELECTRON
+ ? `https://www.last.fm/api/auth/?api_key=${apiKey}&cb=${baseUrl}/#/lastfm/callback`
+ : `https://www.last.fm/api/auth/?api_key=${apiKey}&cb=${baseUrl}/lastfm/callback`;
+ window.open(url);
+}
+
+export function authGetSession(token) {
+ const signature = md5(
+ `api_key${apiKey}methodauth.getSessiontoken${token}${apiSharedSecret}`
+ ).toString();
+ return axios({
+ url,
+ method: 'GET',
+ params: {
+ method: 'auth.getSession',
+ format: 'json',
+ api_key: apiKey,
+ api_sig: signature,
+ token,
+ },
+ });
+}
+
+export function trackUpdateNowPlaying(params) {
+ params.api_key = apiKey;
+ params.method = 'track.updateNowPlaying';
+ params.sk = JSON.parse(localStorage.getItem('lastfm'))['key'];
+ const signature = sign(params);
+
+ return axios({
+ url,
+ method: 'POST',
+ params: {
+ ...params,
+ api_sig: signature,
+ format: 'json',
+ },
+ });
+}
+
+export function trackScrobble(params) {
+ params.api_key = apiKey;
+ params.method = 'track.scrobble';
+ params.sk = JSON.parse(localStorage.getItem('lastfm'))['key'];
+ const signature = sign(params);
+
+ return axios({
+ url,
+ method: 'POST',
+ params: {
+ ...params,
+ api_sig: signature,
+ format: 'json',
+ },
+ });
+}
diff --git a/src/api/mv.js b/src/api/mv.js
index f294785..ecd2b10 100644
--- a/src/api/mv.js
+++ b/src/api/mv.js
@@ -1,4 +1,4 @@
-import request from "@/utils/request";
+import request from '@/utils/request';
/**
* 获取 mv 数据
@@ -9,8 +9,8 @@ import request from "@/utils/request";
*/
export function mvDetail(mvid) {
return request({
- url: "/mv/detail",
- method: "get",
+ url: '/mv/detail',
+ method: 'get',
params: {
mvid,
timestamp: new Date().getTime(),
@@ -30,8 +30,8 @@ export function mvDetail(mvid) {
*/
export function mvUrl(params) {
return request({
- url: "/mv/url",
- method: "get",
+ url: '/mv/url',
+ method: 'get',
params,
});
}
@@ -43,8 +43,8 @@ export function mvUrl(params) {
*/
export function simiMv(mvid) {
return request({
- url: "/simi/mv",
- method: "get",
+ url: '/simi/mv',
+ method: 'get',
params: { mvid },
});
}
@@ -62,8 +62,8 @@ export function simiMv(mvid) {
export function likeAMV(params) {
params.timestamp = new Date().getTime();
return request({
- url: "/mv/sub",
- method: "post",
+ url: '/mv/sub',
+ method: 'post',
params,
});
}
diff --git a/src/api/others.js b/src/api/others.js
index f63423a..5c3cd63 100644
--- a/src/api/others.js
+++ b/src/api/others.js
@@ -1,5 +1,5 @@
-import request from "@/utils/request";
-import { mapTrackPlayableStatus } from "@/utils/common";
+import request from '@/utils/request';
+import { mapTrackPlayableStatus } from '@/utils/common';
/**
* 搜索
@@ -18,12 +18,33 @@ import { mapTrackPlayableStatus } from "@/utils/common";
*/
export function search(params) {
return request({
- url: "/search",
- method: "get",
+ url: '/search',
+ method: 'get',
params,
- }).then((data) => {
- if (data.result.song !== undefined)
+ }).then(data => {
+ if (data.result?.song !== undefined)
data.result.song.songs = mapTrackPlayableStatus(data.result.song.songs);
return data;
});
}
+
+export function personalFM() {
+ return request({
+ url: '/personal_fm',
+ method: 'get',
+ params: {
+ timestamp: new Date().getTime(),
+ },
+ });
+}
+
+export function fmTrash(id) {
+ return request({
+ url: '/fm_trash',
+ method: 'post',
+ params: {
+ timestamp: new Date().getTime(),
+ id,
+ },
+ });
+}
diff --git a/src/api/playlist.js b/src/api/playlist.js
index b0e5994..08177ea 100644
--- a/src/api/playlist.js
+++ b/src/api/playlist.js
@@ -1,5 +1,5 @@
-import request from "@/utils/request";
-import { mapTrackPlayableStatus } from "@/utils/common";
+import request from '@/utils/request';
+import { mapTrackPlayableStatus } from '@/utils/common';
/**
* 推荐歌单
@@ -11,8 +11,8 @@ import { mapTrackPlayableStatus } from "@/utils/common";
*/
export function recommendPlaylist(params) {
return request({
- url: "/personalized",
- method: "get",
+ url: '/personalized',
+ method: 'get',
params,
});
}
@@ -24,9 +24,12 @@ export function recommendPlaylist(params) {
*/
export function dailyRecommendPlaylist(params) {
return request({
- url: "/recommend/resource",
- method: "get",
- params,
+ url: '/recommend/resource',
+ method: 'get',
+ params: {
+ params,
+ timestamp: Date.now(),
+ },
});
}
/**
@@ -43,14 +46,16 @@ export function getPlaylistDetail(id, noCache = false) {
let params = { id };
if (noCache) params.timestamp = new Date().getTime();
return request({
- url: "/playlist/detail",
- method: "get",
+ url: '/playlist/detail',
+ method: 'get',
params,
- }).then((data) => {
- data.playlist.tracks = mapTrackPlayableStatus(
- data.playlist.tracks,
- data.privileges || []
- );
+ }).then(data => {
+ if (data.playlist) {
+ data.playlist.tracks = mapTrackPlayableStatus(
+ data.playlist.tracks,
+ data.privileges || []
+ );
+ }
return data;
});
}
@@ -67,8 +72,8 @@ export function getPlaylistDetail(id, noCache = false) {
*/
export function highQualityPlaylist(params) {
return request({
- url: "/top/playlist/highquality",
- method: "get",
+ url: '/top/playlist/highquality',
+ method: 'get',
params,
});
}
@@ -86,8 +91,8 @@ export function highQualityPlaylist(params) {
*/
export function topPlaylist(params) {
return request({
- url: "/top/playlist",
- method: "get",
+ url: '/top/playlist',
+ method: 'get',
params,
});
}
@@ -98,8 +103,8 @@ export function topPlaylist(params) {
*/
export function playlistCatlist() {
return request({
- url: "/playlist/catlist",
- method: "get",
+ url: '/playlist/catlist',
+ method: 'get',
});
}
@@ -109,8 +114,8 @@ export function playlistCatlist() {
*/
export function toplists() {
return request({
- url: "/toplist",
- method: "get",
+ url: '/toplist',
+ method: 'get',
});
}
@@ -126,8 +131,8 @@ export function toplists() {
export function subscribePlaylist(params) {
params.timestamp = new Date().getTime();
return request({
- url: "/playlist/subscribe",
- method: "post",
+ url: '/playlist/subscribe',
+ method: 'post',
params,
});
}
@@ -140,8 +145,8 @@ export function subscribePlaylist(params) {
*/
export function deletePlaylist(id) {
return request({
- url: "/playlist/delete",
- method: "post",
+ url: '/playlist/delete',
+ method: 'post',
params: { id },
});
}
@@ -160,8 +165,8 @@ export function deletePlaylist(id) {
export function createPlaylist(params) {
params.timestamp = new Date().getTime();
return request({
- url: "/playlist/create",
- method: "post",
+ url: '/playlist/create',
+ method: 'post',
params,
});
}
@@ -178,8 +183,47 @@ export function createPlaylist(params) {
export function addOrRemoveTrackFromPlaylist(params) {
params.timestamp = new Date().getTime();
return request({
- url: "/playlist/tracks",
- method: "post",
+ url: '/playlist/tracks',
+ method: 'post',
+ params,
+ });
+}
+
+/**
+ * 每日推荐歌曲
+ * 说明 : 调用此接口 , 可获得每日推荐歌曲 ( 需要登录 )
+ * @param {Object} params
+ * @param {string} params.op
+ * @param {string} params.pid
+ */
+export function dailyRecommendTracks() {
+ return request({
+ url: '/recommend/songs',
+ method: 'get',
+ params: { timestamp: new Date().getTime() },
+ }).then(result => {
+ result.data.dailySongs = mapTrackPlayableStatus(
+ result.data.dailySongs,
+ result.data.privileges
+ );
+ return result;
+ });
+}
+
+/**
+ * 心动模式/智能播放
+ * 说明 : 登录后调用此接口 , 可获取心动模式/智能播放列表 必选参数 : id : 歌曲 id
+ * - id : 歌曲 id
+ * - pid : 歌单 id
+ * - sid : 要开始播放的歌曲的 id (可选参数)
+ * @param {Object} params
+ * @param {number=} params.id
+ * @param {number=} params.pid
+ */
+export function intelligencePlaylist(params) {
+ return request({
+ url: '/playmode/intelligence/list',
+ method: 'get',
params,
});
}
diff --git a/src/api/track.js b/src/api/track.js
index 43a7d2a..490c91c 100644
--- a/src/api/track.js
+++ b/src/api/track.js
@@ -1,6 +1,13 @@
-import store from "@/store";
-import request from "@/utils/request";
-import { mapTrackPlayableStatus } from "@/utils/common";
+import store from '@/store';
+import request from '@/utils/request';
+import { mapTrackPlayableStatus } from '@/utils/common';
+import {
+ cacheTrackDetail,
+ getTrackDetailFromCache,
+ cacheLyric,
+ getLyricFromCache,
+} from '@/utils/db';
+
/**
* 获取音乐 url
* 说明 : 使用歌单详情接口后 , 能得到的音乐的 id, 但不能得到的音乐 url, 调用此接口, 传入的音乐 id( 可多个 , 用逗号隔开 ), 可以获取对应的音乐的 url,
@@ -8,51 +15,85 @@ import { mapTrackPlayableStatus } from "@/utils/common";
* @param {string} id - 音乐的 id,例如 id=405998841,33894312
*/
export function getMP3(id) {
- let br =
- store.state.settings?.musicQuality !== undefined
- ? store.state.settings.musicQuality
- : 320000;
+ const getBr = () => {
+ // 当返回的 quality >= 400000时,就会优先返回 hi-res
+ const quality = store.state.settings?.musicQuality ?? '320000';
+ return quality === 'flac' ? '350000' : quality;
+ };
+
return request({
- url: "/song/url",
- method: "get",
+ url: '/song/url',
+ method: 'get',
params: {
id,
- br,
+ br: getBr(),
},
});
}
+
/**
* 获取歌曲详情
* 说明 : 调用此接口 , 传入音乐 id(支持多个 id, 用 , 隔开), 可获得歌曲详情(注意:歌曲封面现在需要通过专辑内容接口获取)
* @param {string} ids - 音乐 id, 例如 ids=405998841,33894312
*/
export function getTrackDetail(ids) {
- return request({
- url: "/song/detail",
- method: "get",
- params: {
- ids,
- },
- }).then((data) => {
- data.songs = mapTrackPlayableStatus(data.songs, data.privileges);
- return data;
+ const fetchLatest = () => {
+ return request({
+ url: '/song/detail',
+ method: 'get',
+ params: {
+ ids,
+ },
+ }).then(data => {
+ data.songs.map(song => {
+ const privileges = data.privileges.find(t => t.id === song.id);
+ cacheTrackDetail(song, privileges);
+ });
+ data.songs = mapTrackPlayableStatus(data.songs, data.privileges);
+ return data;
+ });
+ };
+ fetchLatest();
+
+ let idsInArray = [String(ids)];
+ if (typeof ids === 'string') {
+ idsInArray = ids.split(',');
+ }
+
+ return getTrackDetailFromCache(idsInArray).then(result => {
+ if (result) {
+ result.songs = mapTrackPlayableStatus(result.songs, result.privileges);
+ }
+ return result ?? fetchLatest();
});
}
+
/**
* 获取歌词
* 说明 : 调用此接口 , 传入音乐 id 可获得对应音乐的歌词 ( 不需要登录 )
* @param {number} id - 音乐 id
*/
-
export function getLyric(id) {
- return request({
- url: "/lyric",
- method: "get",
- params: {
- id,
- },
+ const fetchLatest = () => {
+ return request({
+ url: '/lyric',
+ method: 'get',
+ params: {
+ id,
+ },
+ }).then(result => {
+ cacheLyric(id, result);
+ return result;
+ });
+ };
+
+ fetchLatest();
+
+ return getLyricFromCache(id).then(result => {
+ return result ?? fetchLatest();
});
}
+
/**
* 新歌速递
* 说明 : 调用此接口 , 可获取新歌速递
@@ -60,13 +101,14 @@ export function getLyric(id) {
*/
export function topSong(type) {
return request({
- url: "/top/song",
- method: "get",
+ url: '/top/song',
+ method: 'get',
params: {
type,
},
});
}
+
/**
* 喜欢音乐
* 说明 : 调用此接口 , 传入音乐 id, 可喜欢该音乐
@@ -79,8 +121,8 @@ export function topSong(type) {
export function likeATrack(params) {
params.timestamp = new Date().getTime();
return request({
- url: "/like",
- method: "get",
+ url: '/like',
+ method: 'get',
params,
});
}
@@ -99,8 +141,8 @@ export function likeATrack(params) {
export function scrobble(params) {
params.timestamp = new Date().getTime();
return request({
- url: "/scrobble",
- method: "get",
+ url: '/scrobble',
+ method: 'get',
params,
});
}
diff --git a/src/api/user.js b/src/api/user.js
index aeeb719..ec02014 100644
--- a/src/api/user.js
+++ b/src/api/user.js
@@ -1,4 +1,4 @@
-import request from "@/utils/request";
+import request from '@/utils/request';
/**
* 获取用户详情
@@ -8,10 +8,25 @@ import request from "@/utils/request";
*/
export function userDetail(uid) {
return request({
- url: "/user/detail",
- method: "get",
+ url: '/user/detail',
+ method: 'get',
params: {
uid,
+ timestamp: new Date().getTime(),
+ },
+ });
+}
+
+/**
+ * 获取账号详情
+ * 说明 : 登录后调用此接口 ,可获取用户账号信息
+ */
+export function userAccount() {
+ return request({
+ url: '/user/account',
+ method: 'get',
+ params: {
+ timestamp: new Date().getTime(),
},
});
}
@@ -29,8 +44,25 @@ export function userDetail(uid) {
*/
export function userPlaylist(params) {
return request({
- url: "/user/playlist",
- method: "get",
+ url: '/user/playlist',
+ method: 'get',
+ params,
+ });
+}
+
+/**
+ * 获取用户播放记录
+ * 说明 : 登录后调用此接口 , 传入用户 id, 可获取用户播放记录
+ * - uid : 用户 id
+ * - type : type=1 时只返回 weekData, type=0 时返回 allData
+ * @param {Object} params
+ * @param {number} params.uid
+ * @param {number} params.type
+ */
+export function userPlayHistory(params) {
+ return request({
+ url: '/user/record',
+ method: 'get',
params,
});
}
@@ -43,8 +75,8 @@ export function userPlaylist(params) {
*/
export function userLikedSongsIDs(uid) {
return request({
- url: "/likelist",
- method: "get",
+ url: '/likelist',
+ method: 'get',
params: {
uid,
timestamp: new Date().getTime(),
@@ -60,8 +92,8 @@ export function userLikedSongsIDs(uid) {
*/
export function dailySignin(type = 0) {
return request({
- url: "/daily_signin",
- method: "post",
+ url: '/daily_signin',
+ method: 'post',
params: {
type,
timestamp: new Date().getTime(),
@@ -72,17 +104,18 @@ export function dailySignin(type = 0) {
/**
* 获取收藏的专辑(需要登录)
* 说明 : 调用此接口可获取到用户收藏的专辑
- * - limit : 返回数量 , 默认为 30
- * - offset : 偏移数量,用于分页 , 如 :( 页数 -1)*30, 其中 30 为 limit 的值 , 默认为 0
+ * - limit : 返回数量 , 默认为 25
+ * - offset : 偏移数量,用于分页 , 如 :( 页数 -1)*25, 其中 25 为 limit 的值 , 默认为 0
* @param {Object} params
* @param {number} params.limit
* @param {number=} params.offset
*/
-export function likedAlbums() {
+export function likedAlbums(params) {
return request({
- url: "/album/sublist",
- method: "get",
+ url: '/album/sublist',
+ method: 'get',
params: {
+ limit: params.limit,
timestamp: new Date().getTime(),
},
});
@@ -92,11 +125,12 @@ export function likedAlbums() {
* 获取收藏的歌手(需要登录)
* 说明 : 调用此接口可获取到用户收藏的歌手
*/
-export function likedArtists() {
+export function likedArtists(params) {
return request({
- url: "/artist/sublist",
- method: "get",
+ url: '/artist/sublist',
+ method: 'get',
params: {
+ limit: params.limit,
timestamp: new Date().getTime(),
},
});
@@ -106,12 +140,82 @@ export function likedArtists() {
* 获取收藏的MV(需要登录)
* 说明 : 调用此接口可获取到用户收藏的MV
*/
-export function likedMVs() {
+export function likedMVs(params) {
return request({
- url: "/mv/sublist",
- method: "get",
+ url: '/mv/sublist',
+ method: 'get',
params: {
+ limit: params.limit,
timestamp: new Date().getTime(),
},
});
}
+
+/**
+ * 上传歌曲到云盘(需要登录)
+ */
+export function uploadSong(file) {
+ let formData = new FormData();
+ formData.append('songFile', file);
+ return request({
+ url: '/cloud',
+ method: 'post',
+ params: {
+ timestamp: new Date().getTime(),
+ },
+ data: formData,
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ timeout: 200000,
+ }).catch(error => {
+ alert(`上传失败,Error: ${error}`);
+ });
+}
+
+/**
+ * 获取云盘歌曲(需要登录)
+ * 说明 : 登录后调用此接口 , 可获取云盘数据 , 获取的数据没有对应 url, 需要再调用一 次 /song/url 获取 url
+ * - limit : 返回数量 , 默认为 200
+ * - offset : 偏移数量,用于分页 , 如 :( 页数 -1)*200, 其中 200 为 limit 的值 , 默认为 0
+ * @param {Object} params
+ * @param {number} params.limit
+ * @param {number=} params.offset
+ */
+export function cloudDisk(params = {}) {
+ params.timestamp = new Date().getTime();
+ return request({
+ url: '/user/cloud',
+ method: 'get',
+ params,
+ });
+}
+
+/**
+ * 获取云盘歌曲详情(需要登录)
+ */
+export function cloudDiskTrackDetail(id) {
+ return request({
+ url: '/user/cloud/detail',
+ method: 'get',
+ params: {
+ timestamp: new Date().getTime(),
+ id,
+ },
+ });
+}
+
+/**
+ * 删除云盘歌曲(需要登录)
+ * @param {Array} id
+ */
+export function cloudDiskTrackDelete(id) {
+ return request({
+ url: '/user/cloud/del',
+ method: 'get',
+ params: {
+ timestamp: new Date().getTime(),
+ id,
+ },
+ });
+}
diff --git a/src/assets/css/global.scss b/src/assets/css/global.scss
new file mode 100644
index 0000000..00c3915
--- /dev/null
+++ b/src/assets/css/global.scss
@@ -0,0 +1,135 @@
+@font-face {
+ font-family: 'Barlow';
+ font-weight: normal;
+ src: url('~@/assets/fonts/Barlow-Regular.woff2') format('woff2'),
+ url('~@/assets/fonts/Barlow-Regular.ttf') format('truetype');
+}
+@font-face {
+ font-family: 'Barlow';
+ font-weight: medium;
+ src: url('~@/assets/fonts/Barlow-Medium.woff2') format('woff2'),
+ url('~@/assets/fonts/Barlow-Medium.ttf') format('truetype');
+}
+@font-face {
+ font-family: 'Barlow';
+ font-weight: 600;
+ src: url('~@/assets/fonts/Barlow-SemiBold.woff2') format('woff2'),
+ url('~@/assets/fonts/Barlow-SemiBold.ttf') format('truetype');
+}
+@font-face {
+ font-family: 'Barlow';
+ font-weight: bold;
+ src: url('~@/assets/fonts/Barlow-Bold.woff2') format('woff2'),
+ url('~@/assets/fonts/Barlow-Bold.ttf') format('truetype');
+}
+@font-face {
+ font-family: 'Barlow';
+ font-weight: 800;
+ src: url('~@/assets/fonts/Barlow-ExtraBold.woff2') format('woff2'),
+ url('~@/assets/fonts/Barlow-ExtraBold.ttf') format('truetype');
+}
+@font-face {
+ font-family: 'Barlow';
+ font-weight: 900;
+ src: url('~@/assets/fonts/Barlow-Black.woff2') format('woff2'),
+ url('~@/assets/fonts/Barlow-Black.ttf') format('truetype');
+}
+
+:root {
+ --color-body-bg: #ffffff;
+ --color-text: #000;
+ --color-primary: #335eea;
+ --color-primary-bg: #eaeffd;
+ --color-secondary: #7a7a7b;
+ --color-secondary-bg: #f5f5f7;
+ --color-navbar-bg: rgba(255, 255, 255, 0.86);
+ --color-primary-bg-for-transparent: rgba(189, 207, 255, 0.28);
+ --color-secondary-bg-for-transparent: rgba(209, 209, 214, 0.28);
+ --html-overflow-y: overlay;
+}
+
+[data-theme='dark'] {
+ --color-body-bg: #222222;
+ --color-text: #ffffff;
+ --color-primary: #335eea;
+ --color-primary-bg: #bbcdff;
+ --color-secondary: #7a7a7b;
+ --color-secondary-bg: #323232;
+ --color-navbar-bg: rgba(34, 34, 34, 0.86);
+ --color-primary-bg-for-transparent: rgba(255, 255, 255, 0.12);
+ --color-secondary-bg-for-transparent: rgba(255, 255, 255, 0.08);
+}
+
+#app,
+input {
+ font-family: 'Barlow', ui-sans-serif, system-ui, -apple-system,
+ BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei,
+ Source Han Sans SC, Noto Sans CJK SC, WenQuanYi Micro Hei, sans-serif,
+ microsoft uighur;
+}
+body {
+ background-color: var(--color-body-bg);
+}
+
+html {
+ overflow-y: var(--html-overflow-y);
+ min-width: 768px;
+ overscroll-behavior: none;
+}
+
+select,
+button {
+ font-family: inherit;
+}
+button {
+ background: none;
+ border: none;
+ cursor: pointer;
+ user-select: none;
+}
+input,
+button {
+ &:focus {
+ outline: none;
+ }
+}
+a {
+ color: inherit;
+ text-decoration: none;
+ cursor: pointer;
+ &:hover {
+ text-decoration: underline;
+ }
+}
+
+[data-electron='yes'] {
+ button,
+ .navigation-links a,
+ .playlist-info .description {
+ cursor: default !important;
+ }
+}
+
+::-webkit-scrollbar {
+ width: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: transparent;
+ border-left: 1px solid rgba(128, 128, 128, 0.18);
+ background: var(--color-body-bg);
+}
+
+::-webkit-scrollbar-thumb {
+ -webkit-border-radius: 10px;
+ border-radius: 10px;
+ background: rgba(128, 128, 128, 0.38);
+}
+
+[data-theme='dark'] ::-webkit-scrollbar-thumb {
+ background: var(--color-secondary-bg);
+}
+
+.user-select-none {
+ user-select: none;
+}
diff --git a/src/assets/css/plyr.css b/src/assets/css/plyr.css
index 584ba89..1a128f0 100644
--- a/src/assets/css/plyr.css
+++ b/src/assets/css/plyr.css
@@ -265,17 +265,17 @@ a.plyr__control::before {
display: none;
}
-.plyr [data-plyr="airplay"],
-.plyr [data-plyr="captions"],
-.plyr [data-plyr="fullscreen"],
-.plyr [data-plyr="pip"] {
+.plyr [data-plyr='airplay'],
+.plyr [data-plyr='captions'],
+.plyr [data-plyr='fullscreen'],
+.plyr [data-plyr='pip'] {
display: none;
}
-.plyr--airplay-supported [data-plyr="airplay"],
-.plyr--captions-enabled [data-plyr="captions"],
-.plyr--fullscreen-enabled [data-plyr="fullscreen"],
-.plyr--pip-supported [data-plyr="pip"] {
+.plyr--airplay-supported [data-plyr='airplay'],
+.plyr--captions-enabled [data-plyr='captions'],
+.plyr--fullscreen-enabled [data-plyr='fullscreen'],
+.plyr--pip-supported [data-plyr='pip'] {
display: inline-block;
}
@@ -288,11 +288,11 @@ a.plyr__control::before {
transition: transform 0.3s ease;
}
-.plyr__menu .plyr__control[aria-expanded="true"] svg {
+.plyr__menu .plyr__control[aria-expanded='true'] svg {
transform: rotate(90deg);
}
-.plyr__menu .plyr__control[aria-expanded="true"] .plyr__tooltip {
+.plyr__menu .plyr__control[aria-expanded='true'] .plyr__tooltip {
display: none;
}
@@ -327,7 +327,7 @@ a.plyr__control::before {
border: var(--plyr-menu-arrow-size, 4px) solid transparent;
border-top-color: rgba(255, 255, 255, 0.9);
border-top-color: var(--plyr-menu-background, rgba(255, 255, 255, 0.9));
- content: "";
+ content: '';
height: 0;
position: absolute;
right: calc(((18px / 2) + calc(10px * 0.7)) - (4px / 2));
@@ -341,18 +341,18 @@ a.plyr__control::before {
width: 0;
}
-.plyr__menu__container [role="menu"] {
+.plyr__menu__container [role='menu'] {
padding: calc(10px * 0.7);
padding: calc(var(--plyr-control-spacing, 10px) * 0.7);
}
-.plyr__menu__container [role="menuitem"],
-.plyr__menu__container [role="menuitemradio"] {
+.plyr__menu__container [role='menuitem'],
+.plyr__menu__container [role='menuitemradio'] {
margin-top: 2px;
}
-.plyr__menu__container [role="menuitem"]:first-child,
-.plyr__menu__container [role="menuitemradio"]:first-child {
+.plyr__menu__container [role='menuitem']:first-child,
+.plyr__menu__container [role='menuitemradio']:first-child {
margin-top: 0;
}
@@ -386,7 +386,7 @@ a.plyr__control::before {
.plyr__menu__container .plyr__control::after {
border: 4px solid transparent;
border: var(--plyr-menu-item-arrow-size, 4px) solid transparent;
- content: "";
+ content: '';
position: absolute;
top: 50%;
transform: translateY(-50%);
@@ -441,7 +441,7 @@ a.plyr__control::before {
background: var(--plyr-menu-back-border-color, #dcdfe5);
box-shadow: 0 1px 0 #fff;
box-shadow: 0 1px 0 var(--plyr-menu-back-border-shadow-color, #fff);
- content: "";
+ content: '';
height: 1px;
left: 0;
margin-top: calc(calc(10px * 0.7) / 2);
@@ -457,19 +457,19 @@ a.plyr__control::before {
border-right-color: currentColor;
}
-.plyr__menu__container .plyr__control[role="menuitemradio"] {
+.plyr__menu__container .plyr__control[role='menuitemradio'] {
padding-left: calc(10px * 0.7);
padding-left: calc(var(--plyr-control-spacing, 10px) * 0.7);
}
-.plyr__menu__container .plyr__control[role="menuitemradio"]::after,
-.plyr__menu__container .plyr__control[role="menuitemradio"]::before {
+.plyr__menu__container .plyr__control[role='menuitemradio']::after,
+.plyr__menu__container .plyr__control[role='menuitemradio']::before {
border-radius: 100%;
}
-.plyr__menu__container .plyr__control[role="menuitemradio"]::before {
+.plyr__menu__container .plyr__control[role='menuitemradio']::before {
background: rgba(0, 0, 0, 0.1);
- content: "";
+ content: '';
display: block;
flex-shrink: 0;
height: 16px;
@@ -479,7 +479,7 @@ a.plyr__control::before {
width: 16px;
}
-.plyr__menu__container .plyr__control[role="menuitemradio"]::after {
+.plyr__menu__container .plyr__control[role='menuitemradio']::after {
background: #fff;
border: 0;
height: 6px;
@@ -492,7 +492,7 @@ a.plyr__control::before {
}
.plyr__menu__container
- .plyr__control[role="menuitemradio"][aria-checked="true"]::before {
+ .plyr__control[role='menuitemradio'][aria-checked='true']::before {
background: #00b3ff;
background: var(
--plyr-control-toggle-checked-background,
@@ -501,14 +501,14 @@ a.plyr__control::before {
}
.plyr__menu__container
- .plyr__control[role="menuitemradio"][aria-checked="true"]::after {
+ .plyr__control[role='menuitemradio'][aria-checked='true']::after {
opacity: 1;
transform: translateY(-50%) scale(1);
}
.plyr__menu__container
- .plyr__control[role="menuitemradio"].plyr__tab-focus::before,
-.plyr__menu__container .plyr__control[role="menuitemradio"]:hover::before {
+ .plyr__control[role='menuitemradio'].plyr__tab-focus::before,
+.plyr__menu__container .plyr__control[role='menuitemradio']:hover::before {
background: rgba(35, 40, 47, 0.1);
}
@@ -524,7 +524,7 @@ a.plyr__control::before {
pointer-events: none;
}
-.plyr--full-ui input[type="range"] {
+.plyr--full-ui input[type='range'] {
-webkit-appearance: none;
background: 0 0;
border: 0;
@@ -547,7 +547,7 @@ a.plyr__control::before {
width: 100%;
}
-.plyr--full-ui input[type="range"]::-webkit-slider-runnable-track {
+.plyr--full-ui input[type='range']::-webkit-slider-runnable-track {
background: 0 0;
border: 0;
border-radius: calc(5px / 2);
@@ -566,7 +566,7 @@ a.plyr__control::before {
);
}
-.plyr--full-ui input[type="range"]::-webkit-slider-thumb {
+.plyr--full-ui input[type='range']::-webkit-slider-thumb {
background: #fff;
background: var(--plyr-range-thumb-background, #fff);
border: 0;
@@ -596,7 +596,7 @@ a.plyr__control::before {
);
}
-.plyr--full-ui input[type="range"]::-moz-range-track {
+.plyr--full-ui input[type='range']::-moz-range-track {
background: 0 0;
border: 0;
border-radius: calc(5px / 2);
@@ -608,7 +608,7 @@ a.plyr__control::before {
user-select: none;
}
-.plyr--full-ui input[type="range"]::-moz-range-thumb {
+.plyr--full-ui input[type='range']::-moz-range-thumb {
background: #fff;
background: var(--plyr-range-thumb-background, #fff);
border: 0;
@@ -628,7 +628,7 @@ a.plyr__control::before {
width: var(--plyr-range-thumb-height, 13px);
}
-.plyr--full-ui input[type="range"]::-moz-range-progress {
+.plyr--full-ui input[type='range']::-moz-range-progress {
background: currentColor;
border-radius: calc(5px / 2);
border-radius: calc(var(--plyr-range-track-height, 5px) / 2);
@@ -636,7 +636,7 @@ a.plyr__control::before {
height: var(--plyr-range-track-height, 5px);
}
-.plyr--full-ui input[type="range"]::-ms-track {
+.plyr--full-ui input[type='range']::-ms-track {
background: 0 0;
border: 0;
border-radius: calc(5px / 2);
@@ -650,7 +650,7 @@ a.plyr__control::before {
color: transparent;
}
-.plyr--full-ui input[type="range"]::-ms-fill-upper {
+.plyr--full-ui input[type='range']::-ms-fill-upper {
background: 0 0;
border: 0;
border-radius: calc(5px / 2);
@@ -663,7 +663,7 @@ a.plyr__control::before {
user-select: none;
}
-.plyr--full-ui input[type="range"]::-ms-fill-lower {
+.plyr--full-ui input[type='range']::-ms-fill-lower {
background: 0 0;
border: 0;
border-radius: calc(5px / 2);
@@ -677,7 +677,7 @@ a.plyr__control::before {
background: currentColor;
}
-.plyr--full-ui input[type="range"]::-ms-thumb {
+.plyr--full-ui input[type='range']::-ms-thumb {
background: #fff;
background: var(--plyr-range-thumb-background, #fff);
border: 0;
@@ -698,20 +698,20 @@ a.plyr__control::before {
margin-top: 0;
}
-.plyr--full-ui input[type="range"]::-ms-tooltip {
+.plyr--full-ui input[type='range']::-ms-tooltip {
display: none;
}
-.plyr--full-ui input[type="range"]:focus {
+.plyr--full-ui input[type='range']:focus {
outline: 0;
}
-.plyr--full-ui input[type="range"]::-moz-focus-outer {
+.plyr--full-ui input[type='range']::-moz-focus-outer {
border: 0;
}
.plyr--full-ui
- input[type="range"].plyr__tab-focus::-webkit-slider-runnable-track {
+ input[type='range'].plyr__tab-focus::-webkit-slider-runnable-track {
outline-color: #00b3ff;
outline-color: var(
--plyr-tab-focus-color,
@@ -722,7 +722,7 @@ a.plyr__control::before {
outline-width: 3px;
}
-.plyr--full-ui input[type="range"].plyr__tab-focus::-moz-range-track {
+.plyr--full-ui input[type='range'].plyr__tab-focus::-moz-range-track {
outline-color: #00b3ff;
outline-color: var(
--plyr-tab-focus-color,
@@ -733,7 +733,7 @@ a.plyr__control::before {
outline-width: 3px;
}
-.plyr--full-ui input[type="range"].plyr__tab-focus::-ms-track {
+.plyr--full-ui input[type='range'].plyr__tab-focus::-ms-track {
outline-color: #00b3ff;
outline-color: var(
--plyr-tab-focus-color,
@@ -769,7 +769,7 @@ a.plyr__control::before {
}
.plyr__time + .plyr__time::before {
- content: "\2044";
+ content: '\2044';
margin-right: 10px;
margin-right: var(--plyr-control-spacing, 10px);
}
@@ -821,7 +821,7 @@ a.plyr__control::before {
var(--plyr-tooltip-background, rgba(255, 255, 255, 0.9));
bottom: calc(4px * -1);
bottom: calc(var(--plyr-tooltip-arrow-size, 4px) * -1);
- content: "";
+ content: '';
height: 0;
left: 50%;
position: absolute;
@@ -906,7 +906,7 @@ a.plyr__control::before {
position: relative;
}
-.plyr__progress input[type="range"],
+.plyr__progress input[type='range'],
.plyr__progress__buffer {
margin-left: calc(13px * -0.5);
margin-left: calc(var(--plyr-range-thumb-height, 13px) * -0.5);
@@ -916,7 +916,7 @@ a.plyr__control::before {
width: calc(100% + var(--plyr-range-thumb-height, 13px));
}
-.plyr__progress input[type="range"] {
+.plyr__progress input[type='range'] {
position: relative;
z-index: 2;
}
@@ -1024,7 +1024,7 @@ a.plyr__control::before {
width: 20%;
}
-.plyr__volume input[type="range"] {
+.plyr__volume input[type='range'] {
margin-left: calc(10px / 2);
margin-left: calc(var(--plyr-control-spacing, 10px) / 2);
margin-right: calc(10px / 2);
@@ -1054,7 +1054,7 @@ a.plyr__control::before {
.plyr--audio .plyr__control.plyr__tab-focus,
.plyr--audio .plyr__control:hover,
-.plyr--audio .plyr__control[aria-expanded="true"] {
+.plyr--audio .plyr__control[aria-expanded='true'] {
background: #00b3ff;
background: var(
--plyr-audio-control-background-hover,
@@ -1064,7 +1064,7 @@ a.plyr__control::before {
color: var(--plyr-audio-control-color-hover, #fff);
}
-.plyr--full-ui.plyr--audio input[type="range"]::-webkit-slider-runnable-track {
+.plyr--full-ui.plyr--audio input[type='range']::-webkit-slider-runnable-track {
background-color: rgba(193, 200, 209, 0.6);
background-color: var(
--plyr-audio-range-track-background,
@@ -1072,7 +1072,7 @@ a.plyr__control::before {
);
}
-.plyr--full-ui.plyr--audio input[type="range"]::-moz-range-track {
+.plyr--full-ui.plyr--audio input[type='range']::-moz-range-track {
background-color: rgba(193, 200, 209, 0.6);
background-color: var(
--plyr-audio-range-track-background,
@@ -1080,7 +1080,7 @@ a.plyr__control::before {
);
}
-.plyr--full-ui.plyr--audio input[type="range"]::-ms-track {
+.plyr--full-ui.plyr--audio input[type='range']::-ms-track {
background-color: rgba(193, 200, 209, 0.6);
background-color: var(
--plyr-audio-range-track-background,
@@ -1088,7 +1088,7 @@ a.plyr__control::before {
);
}
-.plyr--full-ui.plyr--audio input[type="range"]:active::-webkit-slider-thumb {
+.plyr--full-ui.plyr--audio input[type='range']:active::-webkit-slider-thumb {
box-shadow: 0 1px 1px rgba(35, 40, 47, 0.15), 0 0 0 1px rgba(35, 40, 47, 0.2),
0 0 0 3px rgba(35, 40, 47, 0.1);
box-shadow: var(
@@ -1100,7 +1100,7 @@ a.plyr__control::before {
var(--plyr-audio-range-thumb-active-shadow-color, rgba(35, 40, 47, 0.1));
}
-.plyr--full-ui.plyr--audio input[type="range"]:active::-moz-range-thumb {
+.plyr--full-ui.plyr--audio input[type='range']:active::-moz-range-thumb {
box-shadow: 0 1px 1px rgba(35, 40, 47, 0.15), 0 0 0 1px rgba(35, 40, 47, 0.2),
0 0 0 3px rgba(35, 40, 47, 0.1);
box-shadow: var(
@@ -1112,7 +1112,7 @@ a.plyr__control::before {
var(--plyr-audio-range-thumb-active-shadow-color, rgba(35, 40, 47, 0.1));
}
-.plyr--full-ui.plyr--audio input[type="range"]:active::-ms-thumb {
+.plyr--full-ui.plyr--audio input[type='range']:active::-ms-thumb {
box-shadow: 0 1px 1px rgba(35, 40, 47, 0.15), 0 0 0 1px rgba(35, 40, 47, 0.2),
0 0 0 3px rgba(35, 40, 47, 0.1);
box-shadow: var(
@@ -1207,7 +1207,7 @@ a.plyr__control::before {
.plyr--video .plyr__control.plyr__tab-focus,
.plyr--video .plyr__control:hover,
-.plyr--video .plyr__control[aria-expanded="true"] {
+.plyr--video .plyr__control[aria-expanded='true'] {
background: #00b3ff;
background: var(
--plyr-video-control-background-hover,
@@ -1258,7 +1258,7 @@ a.plyr__control::before {
display: block;
}
-.plyr--full-ui.plyr--video input[type="range"]::-webkit-slider-runnable-track {
+.plyr--full-ui.plyr--video input[type='range']::-webkit-slider-runnable-track {
background-color: rgba(255, 255, 255, 0.25);
background-color: var(
--plyr-video-range-track-background,
@@ -1266,7 +1266,7 @@ a.plyr__control::before {
);
}
-.plyr--full-ui.plyr--video input[type="range"]::-moz-range-track {
+.plyr--full-ui.plyr--video input[type='range']::-moz-range-track {
background-color: rgba(255, 255, 255, 0.25);
background-color: var(
--plyr-video-range-track-background,
@@ -1274,7 +1274,7 @@ a.plyr__control::before {
);
}
-.plyr--full-ui.plyr--video input[type="range"]::-ms-track {
+.plyr--full-ui.plyr--video input[type='range']::-ms-track {
background-color: rgba(255, 255, 255, 0.25);
background-color: var(
--plyr-video-range-track-background,
@@ -1282,7 +1282,7 @@ a.plyr__control::before {
);
}
-.plyr--full-ui.plyr--video input[type="range"]:active::-webkit-slider-thumb {
+.plyr--full-ui.plyr--video input[type='range']:active::-webkit-slider-thumb {
box-shadow: 0 1px 1px rgba(35, 40, 47, 0.15), 0 0 0 1px rgba(35, 40, 47, 0.2),
0 0 0 3px rgba(255, 255, 255, 0.5);
box-shadow: var(
@@ -1297,7 +1297,7 @@ a.plyr__control::before {
);
}
-.plyr--full-ui.plyr--video input[type="range"]:active::-moz-range-thumb {
+.plyr--full-ui.plyr--video input[type='range']:active::-moz-range-thumb {
box-shadow: 0 1px 1px rgba(35, 40, 47, 0.15), 0 0 0 1px rgba(35, 40, 47, 0.2),
0 0 0 3px rgba(255, 255, 255, 0.5);
box-shadow: var(
@@ -1312,7 +1312,7 @@ a.plyr__control::before {
);
}
-.plyr--full-ui.plyr--video input[type="range"]:active::-ms-thumb {
+.plyr--full-ui.plyr--video input[type='range']:active::-ms-thumb {
box-shadow: 0 1px 1px rgba(35, 40, 47, 0.15), 0 0 0 1px rgba(35, 40, 47, 0.2),
0 0 0 3px rgba(255, 255, 255, 0.5);
box-shadow: var(
@@ -1713,7 +1713,7 @@ a.plyr__control::before {
var(--plyr-tooltip-background, rgba(255, 255, 255, 0.9));
bottom: calc(4px * -1);
bottom: calc(var(--plyr-tooltip-arrow-size, 4px) * -1);
- content: "";
+ content: '';
height: 0;
left: 50%;
position: absolute;
diff --git a/src/assets/css/slider.css b/src/assets/css/slider.css
index 2e5f40d..071210b 100644
--- a/src/assets/css/slider.css
+++ b/src/assets/css/slider.css
@@ -89,7 +89,7 @@
}
.nyancat .vue-slider-dot-handle {
- background: url("/img/logos/nyancat.gif");
+ background: url('/img/logos/nyancat.gif');
background-size: 36px;
width: 36px;
height: 24px;
@@ -100,6 +100,11 @@
visibility: visible;
}
+.nyancat-stop .vue-slider-dot-handle {
+ background-image: url('/img/logos/nyancat-stop.png');
+ transition: 300ms;
+}
+
/* lyrics */
.lyrics-page .vue-slider-rail {
background-color: rgba(128, 128, 128, 0.18);
@@ -121,10 +126,19 @@
display: none;
}
-body[data-theme="dark"] .lyrics-page .vue-slider-process {
+body[data-theme='dark'] .lyrics-page .vue-slider-process {
background-color: #fafafa;
}
-body[data-theme="dark"] .lyrics-page .vue-slider-dot-handle {
+body[data-theme='dark'] .lyrics-page .vue-slider-dot-handle {
+ background-color: #fff;
+}
+
+.lyrics-page[data-theme='dark'] .vue-slider-rail {
+ background-color: rgba(255, 255, 255, 0.18);
+}
+
+.lyrics-page[data-theme='dark'] .vue-slider-process,
+.lyrics-page[data-theme='dark'] .vue-slider-dot-handle {
background-color: #fff;
}
diff --git a/src/assets/fonts/Barlow-Black.ttf b/src/assets/fonts/Barlow-Black.ttf
new file mode 100755
index 0000000..c4a4b05
Binary files /dev/null and b/src/assets/fonts/Barlow-Black.ttf differ
diff --git a/src/assets/fonts/Barlow-Black.woff2 b/src/assets/fonts/Barlow-Black.woff2
new file mode 100644
index 0000000..b3b05b0
Binary files /dev/null and b/src/assets/fonts/Barlow-Black.woff2 differ
diff --git a/src/assets/fonts/Barlow-Bold.ttf b/src/assets/fonts/Barlow-Bold.ttf
new file mode 100755
index 0000000..cf78fd4
Binary files /dev/null and b/src/assets/fonts/Barlow-Bold.ttf differ
diff --git a/src/assets/fonts/Barlow-Bold.woff2 b/src/assets/fonts/Barlow-Bold.woff2
new file mode 100644
index 0000000..1e3542c
Binary files /dev/null and b/src/assets/fonts/Barlow-Bold.woff2 differ
diff --git a/src/assets/fonts/Barlow-ExtraBold.ttf b/src/assets/fonts/Barlow-ExtraBold.ttf
new file mode 100755
index 0000000..3d5f061
Binary files /dev/null and b/src/assets/fonts/Barlow-ExtraBold.ttf differ
diff --git a/src/assets/fonts/Barlow-ExtraBold.woff2 b/src/assets/fonts/Barlow-ExtraBold.woff2
new file mode 100644
index 0000000..d5789b2
Binary files /dev/null and b/src/assets/fonts/Barlow-ExtraBold.woff2 differ
diff --git a/src/assets/fonts/Barlow-Medium.ttf b/src/assets/fonts/Barlow-Medium.ttf
new file mode 100755
index 0000000..1ed9192
Binary files /dev/null and b/src/assets/fonts/Barlow-Medium.ttf differ
diff --git a/src/assets/fonts/Barlow-Medium.woff2 b/src/assets/fonts/Barlow-Medium.woff2
new file mode 100644
index 0000000..93e9bcb
Binary files /dev/null and b/src/assets/fonts/Barlow-Medium.woff2 differ
diff --git a/src/assets/fonts/Barlow-Regular.ttf b/src/assets/fonts/Barlow-Regular.ttf
new file mode 100755
index 0000000..8bc6a2e
Binary files /dev/null and b/src/assets/fonts/Barlow-Regular.ttf differ
diff --git a/src/assets/fonts/Barlow-Regular.woff2 b/src/assets/fonts/Barlow-Regular.woff2
new file mode 100644
index 0000000..65b4987
Binary files /dev/null and b/src/assets/fonts/Barlow-Regular.woff2 differ
diff --git a/src/assets/fonts/Barlow-SemiBold.ttf b/src/assets/fonts/Barlow-SemiBold.ttf
new file mode 100755
index 0000000..7b7320b
Binary files /dev/null and b/src/assets/fonts/Barlow-SemiBold.ttf differ
diff --git a/src/assets/fonts/Barlow-SemiBold.woff2 b/src/assets/fonts/Barlow-SemiBold.woff2
new file mode 100644
index 0000000..3f582a0
Binary files /dev/null and b/src/assets/fonts/Barlow-SemiBold.woff2 differ
diff --git a/src/assets/icons/arrow-down.svg b/src/assets/icons/arrow-down.svg
new file mode 100644
index 0000000..333ce72
--- /dev/null
+++ b/src/assets/icons/arrow-down.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/arrow-up-alt.svg b/src/assets/icons/arrow-up-alt.svg
new file mode 100644
index 0000000..1980837
--- /dev/null
+++ b/src/assets/icons/arrow-up-alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/arrow-up.svg b/src/assets/icons/arrow-up.svg
new file mode 100644
index 0000000..bc5a0bd
--- /dev/null
+++ b/src/assets/icons/arrow-up.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/circle.svg b/src/assets/icons/circle.svg
deleted file mode 100644
index eb210da..0000000
--- a/src/assets/icons/circle.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/icons/dropdown.svg b/src/assets/icons/dropdown.svg
new file mode 100644
index 0000000..e1d0723
--- /dev/null
+++ b/src/assets/icons/dropdown.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/expand.svg b/src/assets/icons/expand.svg
deleted file mode 100644
index 41efe24..0000000
--- a/src/assets/icons/expand.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/icons/fm.svg b/src/assets/icons/fm.svg
new file mode 100644
index 0000000..fe760cd
--- /dev/null
+++ b/src/assets/icons/fm.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/fullscreen-exit.svg b/src/assets/icons/fullscreen-exit.svg
new file mode 100644
index 0000000..f76f601
--- /dev/null
+++ b/src/assets/icons/fullscreen-exit.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/fullscreen.svg b/src/assets/icons/fullscreen.svg
new file mode 100644
index 0000000..e6128c0
--- /dev/null
+++ b/src/assets/icons/fullscreen.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/index.js b/src/assets/icons/index.js
index 70c0243..ee45a56 100644
--- a/src/assets/icons/index.js
+++ b/src/assets/icons/index.js
@@ -1,8 +1,7 @@
-import Vue from "vue";
-import SvgIcon from "@/components/SvgIcon";
+import Vue from 'vue';
+import SvgIcon from '@/components/SvgIcon';
-Vue.component("svg-icon", SvgIcon);
-const requireAll = (requireContext) =>
- requireContext.keys().map(requireContext);
-const req = require.context("./", true, /\.svg$/);
+Vue.component('svg-icon', SvgIcon);
+const requireAll = requireContext => requireContext.keys().map(requireContext);
+const req = require.context('./', true, /\.svg$/);
requireAll(req);
diff --git a/src/assets/icons/login.svg b/src/assets/icons/login.svg
new file mode 100644
index 0000000..b4acd61
--- /dev/null
+++ b/src/assets/icons/login.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/plus.svg b/src/assets/icons/plus.svg
new file mode 100644
index 0000000..e254d52
--- /dev/null
+++ b/src/assets/icons/plus.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/sort-up.svg b/src/assets/icons/sort-up.svg
new file mode 100644
index 0000000..3efe4cf
--- /dev/null
+++ b/src/assets/icons/sort-up.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/src/assets/icons/thumbs-down.svg b/src/assets/icons/thumbs-down.svg
new file mode 100644
index 0000000..02fe63b
--- /dev/null
+++ b/src/assets/icons/thumbs-down.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/translation.svg b/src/assets/icons/translation.svg
deleted file mode 100644
index 9cbd770..0000000
--- a/src/assets/icons/translation.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
\ No newline at end of file
diff --git a/src/background.js b/src/background.js
index 3f46503..94dbbc0 100644
--- a/src/background.js
+++ b/src/background.js
@@ -1,37 +1,105 @@
-"use strict";
-import { app, protocol, BrowserWindow, shell, dialog } from "electron";
-import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
-import { startNeteaseMusicApi } from "./electron/services";
-import { initIpcMain } from "./electron/ipcMain.js";
-import { createMenu } from "./electron/menu";
-import { createTray } from "@/electron/tray";
-import { createTouchBar } from "./electron/touchBar";
-import { createDockMenu } from "./electron/dockMenu";
-import { autoUpdater } from "electron-updater";
-import installExtension, { VUEJS_DEVTOOLS } from "electron-devtools-installer";
-import express from "express";
-import expressProxy from "express-http-proxy";
-import Store from "electron-store";
+'use strict';
+import {
+ app,
+ protocol,
+ BrowserWindow,
+ shell,
+ dialog,
+ globalShortcut,
+ nativeTheme,
+ screen,
+} from 'electron';
+import {
+ isWindows,
+ isMac,
+ isLinux,
+ isDevelopment,
+ isCreateTray,
+ isCreateMpris,
+} from '@/utils/platform';
+import { createProtocol } from 'vue-cli-plugin-electron-builder/lib';
+import { startNeteaseMusicApi } from './electron/services';
+import { initIpcMain } from './electron/ipcMain.js';
+import { createMenu } from './electron/menu';
+import { createTray } from '@/electron/tray';
+import { createTouchBar } from './electron/touchBar';
+import { createDockMenu } from './electron/dockMenu';
+import { registerGlobalShortcut } from './electron/globalShortcut';
+import { autoUpdater } from 'electron-updater';
+import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer';
+import { EventEmitter } from 'events';
+import express from 'express';
+import expressProxy from 'express-http-proxy';
+import Store from 'electron-store';
+import { createMpris, createDbus } from '@/electron/mpris';
+import { spawn } from 'child_process';
+const clc = require('cli-color');
+const log = text => {
+ console.log(`${clc.blueBright('[background.js]')} ${text}`);
+};
+
+const closeOnLinux = (e, win, store) => {
+ let closeOpt = store.get('settings.closeAppOption');
+ if (closeOpt !== 'exit') {
+ e.preventDefault();
+ }
+
+ if (closeOpt === 'ask') {
+ dialog
+ .showMessageBox({
+ type: 'info',
+ title: 'Information',
+ cancelId: 2,
+ defaultId: 0,
+ message: '确定要关闭吗?',
+ buttons: ['最小化到托盘', '直接退出'],
+ checkboxLabel: '记住我的选择',
+ })
+ .then(result => {
+ if (result.checkboxChecked && result.response !== 2) {
+ win.webContents.send(
+ 'rememberCloseAppOption',
+ result.response === 0 ? 'minimizeToTray' : 'exit'
+ );
+ }
+
+ if (result.response === 0) {
+ win.hide(); //调用 最小化实例方法
+ } else if (result.response === 1) {
+ win = null;
+ app.exit(); //exit()直接关闭客户端,不会执行quit();
+ }
+ })
+ .catch(err => {
+ log(err);
+ });
+ } else if (closeOpt === 'exit') {
+ win = null;
+ app.quit();
+ } else {
+ win.hide();
+ }
+};
class Background {
constructor() {
this.window = null;
- this.tray = null;
+ this.ypmTrayImpl = null;
this.store = new Store({
windowWidth: {
- width: { type: "number", default: 1440 },
- height: { type: "number", default: 840 },
+ width: { type: 'number', default: 1440 },
+ height: { type: 'number', default: 840 },
},
});
this.neteaseMusicAPI = null;
this.expressApp = null;
- this.willQuitApp = process.platform === "darwin" ? false : true;
+ this.willQuitApp = !isMac;
this.init();
}
init() {
- console.log("initializing");
+ log('initializing');
// Make sure the app is singleton.
if (!app.requestSingleInstanceLock()) return app.quit();
@@ -42,16 +110,21 @@ class Background {
// create Express app
this.createExpressApp();
- // init ipcMain
- initIpcMain(this.window, this.store);
-
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
- { scheme: "app", privileges: { secure: true, standard: true } },
+ { scheme: 'app', privileges: { secure: true, standard: true } },
]);
// handle app events
this.handleAppEvents();
+
+ // disable chromium mpris
+ if (isCreateMpris) {
+ app.commandLine.appendSwitch(
+ 'disable-features',
+ 'HardwareMediaKeyHandling,MediaSessionService'
+ );
+ }
}
async initDevtools() {
@@ -59,162 +132,320 @@ class Background {
try {
await installExtension(VUEJS_DEVTOOLS);
} catch (e) {
- console.error("Vue Devtools failed to install:", e.toString());
+ console.error('Vue Devtools failed to install:', e.toString());
}
// Exit cleanly on request from parent process in development mode.
- if (process.platform === "win32") {
- process.on("message", (data) => {
- if (data === "graceful-exit") {
+ if (isWindows) {
+ process.on('message', data => {
+ if (data === 'graceful-exit') {
app.quit();
}
});
} else {
- process.on("SIGTERM", () => {
+ process.on('SIGTERM', () => {
app.quit();
});
}
}
createExpressApp() {
- console.log("creating express app");
+ log('creating express app');
const expressApp = express();
- expressApp.use("/", express.static(__dirname + "/"));
- expressApp.use("/api", expressProxy("http://127.0.0.1:10754"));
- this.expressApp = expressApp.listen(27232);
+ expressApp.use('/', express.static(__dirname + '/'));
+ expressApp.use('/api', expressProxy('http://127.0.0.1:10754'));
+ expressApp.use('/player', (req, res) => {
+ this.window.webContents
+ .executeJavaScript('window.yesplaymusic.player')
+ .then(result => {
+ res.send({
+ currentTrack: result._isPersonalFM
+ ? result._personalFMTrack
+ : result._currentTrack,
+ progress: result._progress,
+ });
+ });
+ });
+ this.expressApp = expressApp.listen(27232, '127.0.0.1');
}
createWindow() {
- console.log("creating app window");
+ log('creating app window');
- this.window = new BrowserWindow({
- width: this.store.get("window.width") | 1440,
- height: this.store.get("window.height") | 840,
+ const appearance = this.store.get('settings.appearance');
+ const showLibraryDefault = this.store.get('settings.showLibraryDefault');
+
+ const options = {
+ width: this.store.get('window.width') || 1440,
+ height: this.store.get('window.height') || 840,
minWidth: 1080,
minHeight: 720,
- titleBarStyle: "hiddenInset",
+ titleBarStyle: 'hiddenInset',
+ frame: !(
+ isWindows ||
+ (isLinux && this.store.get('settings.linuxEnableCustomTitlebar'))
+ ),
+ title: 'YesPlayMusic',
+ show: false,
webPreferences: {
webSecurity: false,
nodeIntegration: true,
+ enableRemoteModule: true,
+ contextIsolation: false,
},
- });
+ backgroundColor:
+ ((appearance === undefined || appearance === 'auto') &&
+ nativeTheme.shouldUseDarkColors) ||
+ appearance === 'dark'
+ ? '#222'
+ : '#fff',
+ };
+
+ if (this.store.get('window.x') && this.store.get('window.y')) {
+ let x = this.store.get('window.x');
+ let y = this.store.get('window.y');
+
+ let displays = screen.getAllDisplays();
+ let isResetWindiw = false;
+ if (displays.length === 1) {
+ let { bounds } = displays[0];
+ if (
+ x < bounds.x ||
+ x > bounds.x + bounds.width - 50 ||
+ y < bounds.y ||
+ y > bounds.y + bounds.height - 50
+ ) {
+ isResetWindiw = true;
+ }
+ } else {
+ isResetWindiw = true;
+ for (let i = 0; i < displays.length; i++) {
+ let { bounds } = displays[i];
+ if (
+ x > bounds.x &&
+ x < bounds.x + bounds.width &&
+ y > bounds.y &&
+ y < bounds.y - bounds.height
+ ) {
+ // 检测到APP窗口当前处于一个可用的屏幕里,break
+ isResetWindiw = false;
+ break;
+ }
+ }
+ }
+
+ if (!isResetWindiw) {
+ options.x = x;
+ options.y = y;
+ }
+ }
+
+ this.window = new BrowserWindow(options);
// hide menu bar on Microsoft Windows and Linux
this.window.setMenuBarVisibility(false);
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
- this.window.loadURL(process.env.WEBPACK_DEV_SERVER_URL);
+ this.window.loadURL(
+ showLibraryDefault
+ ? `${process.env.WEBPACK_DEV_SERVER_URL}/#/library`
+ : process.env.WEBPACK_DEV_SERVER_URL
+ );
if (!process.env.IS_TEST) this.window.webContents.openDevTools();
} else {
- createProtocol("app");
- this.window.loadURL("http://localhost:27232");
+ createProtocol('app');
+ this.window.loadURL(
+ showLibraryDefault
+ ? 'http://localhost:27232/#/library'
+ : 'http://localhost:27232'
+ );
}
}
checkForUpdates() {
+ if (isDevelopment) return;
+ log('checkForUpdates');
autoUpdater.checkForUpdatesAndNotify();
- const showNewVersionMessage = (info) => {
+ const showNewVersionMessage = info => {
dialog
.showMessageBox({
- title: "发现新版本 v" + info.version,
- message: "发现新版本 v" + info.version,
- detail: "是否前往 Github 下载新版本安装包?",
- buttons: ["下载", "取消"],
- type: "question",
+ title: '发现新版本 v' + info.version,
+ message: '发现新版本 v' + info.version,
+ detail: '是否前往 GitHub 下载新版本安装包?',
+ buttons: ['下载', '取消'],
+ type: 'question',
noLink: true,
})
- .then((result) => {
+ .then(result => {
if (result.response === 0) {
shell.openExternal(
- "https://github.com/qier222/YesPlayMusic/releases"
+ 'https://github.com/qier222/YesPlayMusic/releases'
);
}
});
};
- if (process.platform === "darwin") {
- autoUpdater.on("update-available", (info) => {
- showNewVersionMessage(info);
- });
- }
+ autoUpdater.on('update-available', info => {
+ showNewVersionMessage(info);
+ });
}
handleWindowEvents() {
- this.window.once("ready-to-show", () => {
- console.log("windows ready-to-show event");
+ this.window.once('ready-to-show', () => {
+ log('window ready-to-show event');
this.window.show();
+ this.store.set('window', this.window.getBounds());
});
- this.window.on("close", (e) => {
- console.log("windows close event");
- if (this.willQuitApp) {
- /* the user tried to quit the app */
- this.window = null;
- app.quit();
+ this.window.on('close', e => {
+ log('window close event');
+
+ if (isLinux) {
+ closeOnLinux(e, this.window, this.store);
+ } else if (isMac) {
+ if (this.willQuitApp) {
+ this.window = null;
+ app.quit();
+ } else {
+ e.preventDefault();
+ this.window.hide();
+ }
} else {
- /* the user only tried to close the window */
- e.preventDefault();
- this.window.hide();
+ let closeOpt = this.store.get('settings.closeAppOption');
+ if (this.willQuitApp && (closeOpt === 'exit' || closeOpt === 'ask')) {
+ this.window = null;
+ app.quit();
+ } else {
+ e.preventDefault();
+ this.window.hide();
+ }
}
});
- this.window.on("resize", () => {
- let { height, width } = this.window.getBounds();
- this.store.set("window", { height, width });
+ this.window.on('resized', () => {
+ this.store.set('window', this.window.getBounds());
});
- this.window.on("minimize", () => {
- if (
- ["win32", "linux"].includes(process.platform) &&
- this.store.get("settings.minimizeToTray")
- ) {
- this.tray = createTray(this.window);
- this.window.hide();
- }
+ this.window.on('moved', () => {
+ this.store.set('window', this.window.getBounds());
});
- this.window.webContents.on("new-window", function (e, url) {
+ this.window.on('maximize', () => {
+ this.window.webContents.send('isMaximized', true);
+ });
+
+ this.window.on('unmaximize', () => {
+ this.window.webContents.send('isMaximized', false);
+ });
+
+ this.window.webContents.on('new-window', function (e, url) {
e.preventDefault();
+ log('open url');
+ const excludeHosts = ['www.last.fm'];
+ const exclude = excludeHosts.find(host => url.includes(host));
+ if (exclude) {
+ const newWindow = new BrowserWindow({
+ width: 800,
+ height: 600,
+ titleBarStyle: 'default',
+ title: 'YesPlayMusic',
+ webPreferences: {
+ webSecurity: false,
+ nodeIntegration: true,
+ enableRemoteModule: true,
+ contextIsolation: false,
+ },
+ });
+ newWindow.loadURL(url);
+ return;
+ }
shell.openExternal(url);
});
}
handleAppEvents() {
- app.on("ready", async () => {
+ app.on('ready', async () => {
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
- console.log("app ready event");
+ log('app ready event');
// for development
- if (process.env.NODE_ENV !== "production") {
+ if (isDevelopment) {
this.initDevtools();
}
// create window
this.createWindow();
+ this.window.once('ready-to-show', () => {
+ this.window.show();
+ });
this.handleWindowEvents();
+ // create tray
+ if (isCreateTray) {
+ this.trayEventEmitter = new EventEmitter();
+ this.ypmTrayImpl = createTray(this.window, this.trayEventEmitter);
+ }
+
+ // init ipcMain
+ initIpcMain(this.window, this.store, this.trayEventEmitter);
+
+ // set proxy
+ const proxyRules = this.store.get('proxy');
+ if (proxyRules) {
+ this.window.webContents.session.setProxy({ proxyRules }, result => {
+ log('finished setProxy', result);
+ });
+ }
+
// check for updates
this.checkForUpdates();
// create menu
- createMenu(this.window);
+ createMenu(this.window, this.store);
// create dock menu for macOS
- app.dock.setMenu(createDockMenu(this.window));
+ const createdDockMenu = createDockMenu(this.window);
+ if (createDockMenu && app.dock) app.dock.setMenu(createdDockMenu);
// create touch bar
- this.window.setTouchBar(createTouchBar(this.window));
+ const createdTouchBar = createTouchBar(this.window);
+ if (createdTouchBar) this.window.setTouchBar(createdTouchBar);
+
+ // register global shortcuts
+ if (this.store.get('settings.enableGlobalShortcut') !== false) {
+ registerGlobalShortcut(this.window, this.store);
+ }
+
+ // try to start osdlyrics process on start
+ if (this.store.get('settings.enableOsdlyricsSupport')) {
+ await createDbus(this.window);
+ log('try to start osdlyrics process');
+ const osdlyricsProcess = spawn('osdlyrics');
+
+ osdlyricsProcess.on('error', err => {
+ log(`failed to start osdlyrics: ${err.message}`);
+ });
+
+ osdlyricsProcess.on('exit', (code, signal) => {
+ log(`osdlyrics process exited with code ${code}, signal ${signal}`);
+ });
+ }
+
+ // create mpris
+ if (isCreateMpris) {
+ createMpris(this.window);
+ }
});
- app.on("activate", () => {
+ app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
- console.log("app activate event");
+ log('app activate event');
if (this.window === null) {
this.createWindow();
} else {
@@ -222,19 +453,36 @@ class Background {
}
});
- app.on("window-all-closed", () => {
- if (process.platform !== "darwin") {
+ app.on('window-all-closed', () => {
+ if (!isMac) {
app.quit();
}
});
- app.on("before-quit", () => {
+ app.on('before-quit', () => {
this.willQuitApp = true;
});
- app.on("quit", () => {
+ app.on('quit', () => {
this.expressApp.close();
});
+
+ app.on('will-quit', () => {
+ // unregister all global shortcuts
+ globalShortcut.unregisterAll();
+ });
+
+ if (!isMac) {
+ app.on('second-instance', (e, cl, wd) => {
+ if (this.window) {
+ this.window.show();
+ if (this.window.isMinimized()) {
+ this.window.restore();
+ }
+ this.window.focus();
+ }
+ });
+ }
}
}
diff --git a/src/components/ArtistsInLine.vue b/src/components/ArtistsInLine.vue
index b834f47..e2c619d 100644
--- a/src/components/ArtistsInLine.vue
+++ b/src/components/ArtistsInLine.vue
@@ -1,16 +1,21 @@
{{ computedPrefix }}
-
- {{ ar.name }}
- ,
+
+ {{
+ ar.name
+ }}
+ {{ ar.name }}
+ ,
-
+
diff --git a/src/components/ButtonIcon.vue b/src/components/ButtonIcon.vue
index efa14d7..d6afee3 100644
--- a/src/components/ButtonIcon.vue
+++ b/src/components/ButtonIcon.vue
@@ -4,7 +4,7 @@
diff --git a/src/components/ButtonTwoTone.vue b/src/components/ButtonTwoTone.vue
index d2cde2b..c72739a 100644
--- a/src/components/ButtonTwoTone.vue
+++ b/src/components/ButtonTwoTone.vue
@@ -2,7 +2,7 @@