Skip to content

Commit

Permalink
✨ feat: 新增音乐频谱显示
Browse files Browse the repository at this point in the history
  • Loading branch information
imsyy committed Dec 27, 2023
1 parent 8f416ff commit 59f492e
Show file tree
Hide file tree
Showing 18 changed files with 1,125 additions and 105 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@ module.exports = {
$canNotConnect: true,
$refreshCloudCatch: true,
$cleanAll: true,
$player: true,
},
};
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,6 @@ docker run -d --name SPlayer -p 7899:7899 imsyy/splayer:latest
<details>
<summary>查看目录结构详情</summary>


> ChatGPT 写的,如有错误,请见谅
```dir
Expand Down Expand Up @@ -423,4 +422,5 @@ docker run -d --name SPlayer -p 7899:7899 imsyy/splayer:latest
│   └── Test.vue
└── vercel.json # Vercel 部署配置
```

</details>
13 changes: 7 additions & 6 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ declare module 'vue' {
NDrawer: typeof import('naive-ui')['NDrawer']
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
NDropdown: typeof import('naive-ui')['NDropdown']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NEllipsis: (typeof import("naive-ui"))["NEllipsis"]
NEmpty: typeof import('naive-ui')['NEmpty']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
Expand All @@ -46,8 +46,8 @@ declare module 'vue' {
NGrid: typeof import('naive-ui')['NGrid']
NH1: typeof import('naive-ui')['NH1']
NH3: typeof import('naive-ui')['NH3']
NH4: typeof import('naive-ui')['NH4']
NH6: typeof import('naive-ui')['NH6']
NH4: (typeof import("naive-ui"))["NH4"]
NH6: (typeof import("naive-ui"))["NH6"]
NIcon: typeof import('naive-ui')['NIcon']
NImage: typeof import('naive-ui')['NImage']
NInput: typeof import('naive-ui')['NInput']
Expand All @@ -63,14 +63,14 @@ declare module 'vue' {
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NNumberAnimation: typeof import('naive-ui')['NNumberAnimation']
NNumberAnimation: (typeof import("naive-ui"))["NNumberAnimation"]
NPagination: typeof import('naive-ui')['NPagination']
NPopover: typeof import('naive-ui')['NPopover']
NProgress: typeof import('naive-ui')['NProgress']
NProgress: (typeof import("naive-ui"))["NProgress"]
NQrCode: typeof import('naive-ui')['NQrCode']
NRadio: typeof import('naive-ui')['NRadio']
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NResult: typeof import('naive-ui')['NResult']
NResult: (typeof import("naive-ui"))["NResult"]
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect']
NSkeleton: typeof import('naive-ui')['NSkeleton']
Expand Down Expand Up @@ -101,6 +101,7 @@ declare module 'vue' {
SongListDropdown: typeof import('./src/components/List/SongListDropdown.vue')['default']
SpecialCover: typeof import('./src/components/Cover/SpecialCover.vue')['default']
SpecialCoverCard: typeof import('./src/components/Cover/SpecialCoverCard.vue')['default']
Spectrum: typeof import('./src/components/Player/Spectrum.vue')['default']
SvgIcon: typeof import('./src/components/Global/SvgIcon.vue')['default']
TitleBar: typeof import('./src/components/WinDom/TitleBar.vue')['default']
UpCloudSong: typeof import('./src/components/Modal/UpCloudSong.vue')['default']
Expand Down
40 changes: 19 additions & 21 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" sizes="32x32" href="/images/icons/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/images/icons/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/images/icons/apple-touch-icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>%RENDERER_VITE_SITE_TITLE%</title>
<meta name="apple-mobile-web-app-title" content="%RENDERER_VITE_SITE_TITLE%" />
<meta name="author" content="%RENDERER_VITE_SITE_ANTHOR%" />
<meta name="keywords" content="%RENDERER_VITE_SITE_KEYWORDS%" />
<meta name="description" content="%RENDERER_VITE_SITE_DES%" />
<link rel="mask-icon" href="/images/icons/safari-pinned-tab.svg" color="#5bbad5" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" />
</head>

<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" sizes="32x32" href="/images/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/icons/favicon-16x16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/icons/apple-touch-icon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>%RENDERER_VITE_SITE_TITLE%</title>
<meta name="apple-mobile-web-app-title" content="%RENDERER_VITE_SITE_TITLE%" />
<meta name="author" content="%RENDERER_VITE_SITE_ANTHOR%" />
<meta name="keywords" content="%RENDERER_VITE_SITE_KEYWORDS%" />
<meta name="description" content="%RENDERER_VITE_SITE_DES%" />
<link rel="mask-icon" href="/images/icons/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff" />
</head>

<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>

<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "splayer",
"version": "2.0.0-beta.5",
"version": "2.0.0",
"description": "A minimalist music player",
"main": "./out/main/index.js",
"author": "imsyy",
Expand Down
724 changes: 723 additions & 1 deletion public/font/font.min.css

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
v-if="showSider"
:class="{
'body-layout': true,
'player-bar': Object.keys(music.getPlaySongData)?.length && showPlayBar,
'player-bar': music.getPlaySongData?.id && showPlayBar,
}"
position="absolute"
has-sider
Expand Down Expand Up @@ -43,7 +43,7 @@
v-else
:class="{
'body-layout': true,
'player-bar': Object.keys(music.getPlaySongData)?.length && showPlayBar,
'player-bar': music.getPlaySongData?.id && showPlayBar,
}"
:native-scrollbar="false"
position="absolute"
Expand Down
2 changes: 1 addition & 1 deletion src/components/Global/MainLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<main id="main-layout" :class="['main-layout', { 'no-sider': !showSider }]">
<!-- 回顶 -->
<n-back-top
:bottom="Object.keys(music.getPlaySongData)?.length && showPlayBar ? 110 : 50"
:bottom="music.getPlaySongData?.id && showPlayBar ? 110 : 50"
style="transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
>
<n-icon size="26">
Expand Down
4 changes: 3 additions & 1 deletion src/components/Player/FullPlayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@
</Transition>
<!-- 控制中心 -->
<PlayerControl v-show="playerControlShow" />
<!-- 音乐频谱 -->
<Spectrum v-if="showSpectrums" :show="!playerControlShow" :height="60" />
</div>
</Transition>
</template>
Expand All @@ -242,7 +244,7 @@ const music = musicData();
const status = siteStatus();
const settings = siteSettings();
const { playList, playSongLyric } = storeToRefs(music);
const { playerBackgroundType, showYrc, playCoverType } = storeToRefs(settings);
const { playerBackgroundType, showYrc, playCoverType, showSpectrums } = storeToRefs(settings);
const {
playerControlShow,
controlTimeOut,
Expand Down
7 changes: 5 additions & 2 deletions src/components/Player/MainControl.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<n-card
:class="{
'main-player': true,
'show-bar': Object.keys(music.getPlaySongData)?.length && showPlayBar,
'show-bar': music.getPlaySongData?.id && showPlayBar,
'no-sider': !showSider,
}"
content-style="padding: 0"
Expand Down Expand Up @@ -335,6 +335,7 @@ import {
setVolume,
setVolumeMute,
setRate,
processSpectrum,
} from "@/utils/Player";
import { getSongPlayTime } from "@/utils/timeTools";
import debounce from "@/utils/debounce";
Expand All @@ -361,7 +362,8 @@ const {
playSongLyric,
} = storeToRefs(music);
const { playLoading, playState, playListShow, showPlayBar, showFullPlayer } = storeToRefs(status);
const { showYrc, bottomLyricShow, showSider, showPlaylistCount } = storeToRefs(settings);
const { showYrc, bottomLyricShow, showSider, showPlaylistCount, showSpectrums } =
storeToRefs(settings);

// 子组件
const addPlaylistRef = ref(null);
Expand Down Expand Up @@ -472,6 +474,7 @@ const openFullPlayer = () => {
$message.warning("当前为电台模式,无法开启播放器");
return false;
}
if (showSpectrums.value && typeof $player !== "undefined") processSpectrum($player);
showFullPlayer.value = true;
};

Expand Down
161 changes: 161 additions & 0 deletions src/components/Player/Spectrum.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<!-- 播放器 - 音乐频谱 -->
<template>
<div :style="{ opacity: show ? '0.6' : '0.1' }" class="spectrum">
<canvas ref="canvasRef" :style="{ height: height + 'px' }" class="spectrum-line" />
</div>
</template>

<script setup>
import { storeToRefs } from "pinia";
import { siteStatus } from "@/stores";

const props = defineProps({
show: {
type: Boolean,
default: true,
},
height: {
type: Number,
default: 80,
},
barWidth: {
type: Number,
default: 4,
},
radius: {
type: Number,
default: 2,
},
});
const status = siteStatus();
const { spectrumsData } = storeToRefs(status);

// canvas
const canvasRef = ref(null);
const isKeepDrawing = ref(true);

/**
* 绘制音乐频谱图
* @param {Array} data - 包含音频频谱数据的数组
*/
const drawSpectrum = (data) => {
if (!isKeepDrawing.value) return;
// 设置画布宽度,最大为 1600
canvasRef.value.width = document.body.clientWidth >= 1600 ? 1600 : document.body.clientWidth;
// 设置画布高度
canvasRef.value.height = props.height;
// 获取2D上下文
const ctx = canvasRef.value.getContext("2d");
// 柱形宽度
const barWidth = props.barWidth;
// 圆角半径
const cornerRadius = props.radius;
// 清除画布
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height);
// 遍历音频频谱数据
for (let i = 0; i < 360; i++) {
// 计算柱形高度
const barHeight = (data[i] / 255) * canvasRef.value.height;
// 计算柱形的 x 和 y 坐标
const x = i * (barWidth * 2);
const y = canvasRef.value.height - barHeight;
// 设置柱形颜色,如果未设置则使用默认颜色
ctx.fillStyle = `rgb(${status.coverTheme?.light?.shadeTwo})` || "#efefef";
// 检查柱形高度是否大于0,避免绘制高度为0的柱形
if (barHeight > 0) {
// 调用绘制圆角矩形的函数
roundRect(ctx, x, y, barWidth, barHeight, cornerRadius);
}
}
// 请求下一帧
requestAnimationFrame(() => {
drawSpectrum(spectrumsData.value);
});
};

/**
* 绘制圆角矩形
* @param {CanvasRenderingContext2D} ctx - 2D上下文
* @param {number} x - 矩形左上角 x 坐标
* @param {number} y - 矩形左上角 y 坐标
* @param {number} width - 矩形宽度
* @param {number} height - 矩形高度
* @param {number} radius - 圆角半径
*/
const roundRect = (ctx, x, y, width, height, radius) => {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
ctx.fill();
};

onMounted(() => {
drawSpectrum(spectrumsData.value);
});

onBeforeUnmount(() => {
isKeepDrawing.value = false;
});
</script>

<style lang="scss" scoped>
.spectrum {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
opacity: 0.6;
z-index: -1;
pointer-events: none;
transition: opacity 0.3s;
mask: linear-gradient(
90deg,
hsla(0, 0%, 100%, 0) 0,
hsla(0, 0%, 100%, 0.6) 10%,
#fff 15%,
#fff 85%,
hsla(0, 0%, 100%, 0.6) 90%,
hsla(0, 0%, 100%, 0)
);
-webkit-mask: linear-gradient(
90deg,
hsla(0, 0%, 100%, 0) 0,
hsla(0, 0%, 100%, 0.6) 10%,
#fff 15%,
#fff 85%,
hsla(0, 0%, 100%, 0.6) 90%,
hsla(0, 0%, 100%, 0)
);
.spectrum-line {
mask: linear-gradient(
90deg,
hsla(0, 0%, 100%, 0) 0,
hsla(0, 0%, 100%, 0.6) 5%,
#fff 10%,
#fff 90%,
hsla(0, 0%, 100%, 0.6) 95%,
hsla(0, 0%, 100%, 0)
);
-webkit-mask: linear-gradient(
90deg,
hsla(0, 0%, 100%, 0) 0,
hsla(0, 0%, 100%, 0.6) 5%,
#fff 10%,
#fff 90%,
hsla(0, 0%, 100%, 0.6) 95%,
hsla(0, 0%, 100%, 0)
);
}
}
</style>
2 changes: 2 additions & 0 deletions src/components/Search/SearchInp.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ const toSearch = (val, type = "song") => {
// 取消聚焦状态
status.searchInputFocus = false;
searchInpRef.value?.blur();
// 触发测试
if (Number(val) === 114514) return router.push("/test");
// 判断类型
switch (type) {
// 直接搜索
Expand Down
Loading

0 comments on commit 59f492e

Please sign in to comment.