项目:https://github.com/EveElseIf/MusicParty
当前的项目目录结构
MusicParty/
│
├── BilibiliApi/
│ ├── BilibiliApi.cs
│ └── BilibiliApi.csproj
│
├── MusicApiContract/
│ ├── IMusicApi.cs
│ ├── LoginException.cs
│ ├── Music.cs
│ ├── MusicApiContract.csproj
│ ├── MusicServiceUser.cs
│ ├── PlayList.cs
│ └── PlayableMusic.cs
│
├── MusicParty/
│ ├── Controllers/
│ │ └── ApiController.cs
│ ├── Hub/
│ │ └── MusicHub.cs
│ ├── Properties/
│ │ └── launchSettings.json
│ ├── Extensions.cs
│ ├── MusicBroadcaster.cs
│ ├── MusicParty.csproj
│ ├── MusicProxyMiddleware.cs
│ ├── PreprocessMiddleware.cs
│ ├── Program.cs
│ ├── User.cs
│ ├── UserManager.cs
│ ├── appsettings.Development.json
│ └── appsettings.json
│
├── NeteaseCloudMusicApi/
│ ├── NeteaseCloudMusicApi.cs
│ └── NeteaseCloudMusicApi.csproj
│
├── QQMusicApi/
│ ├── QQMusicApi.cs
│ └── QQMusicApi.csproj
│
├── music-party/
│ ├── pages/
│ │ ├── api/
│ │ │ └── hello.ts
│ │ ├── _app.tsx
│ │ └── index.tsx
│ ├── public/
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── src/
│ │ ├── api/
│ │ │ ├── api.ts
│ │ │ └── musichub.ts
│ │ ├── components/
│ │ │ ├── bilibilibinder.tsx
│ │ │ ├── musicplayer.tsx
│ │ │ ├── musicqueue.tsx
│ │ │ ├── musicselector.tsx
│ │ │ ├── myplaylist.tsx
│ │ │ ├── neteasebinder.tsx
│ │ │ ├── playlist.tsx
│ │ │ └── qqmusicbinder.tsx
│ │ └── utils/
│ │ └── toast.ts
│ ├── styles/
│ │ ├── Home.module.css
│ │ └── globals.css
│ ├── README.md
│ ├── next.config.js
│ ├── package.json
│ ├── pnpm-lock.yaml
│ ├── test.json
│ └── tsconfig.json
│
├── Dockerfile-backend
├── Dockerfile-frontend
├── Dockerfile-neteaseapi
├── Dockerfile-qqmusicapi
├── MusicParty.sln
├── README.md
└── docker-compose.yml当前的修改
https://github.com/EveElseIf/MusicParty/blob/main/BilibiliApi/BilibiliApi.cs
51行
https://api.bilibili.com/x/web-interface/nav91行
https://api.bilibili.com/x/player/wbi/playurl?bvid={ids[0]}&cid={ids[1]}&fnval=16最后在45行添加
_http.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0");重构docker镜像
docker build -t mpmp:latest --file Dockerfile-backend .
docker build -t musicparty-backend:latest --file Dockerfile-backend .
docker build -t musicparty-neteaseapi:latest --file Dockerfile-neteaseapi .
docker build -t musicparty-qqmusicapi:latest --file Dockerfile-qqmusicapi .删除本地镜像
查看
docker images
单个
docker rmi ubuntu:latest
多个
docker rmi limeve/musicparty-qqmusicapi limeve/musicparty-backend limeve/musicparty-frontend mpmp
强制删除(正在运行也删)
docker rmi -f <IMAGE_ID_OR_NAME>
如果镜像正在被容器使用,Docker 默认不会允许你删除该镜像。此时,你可以使用 -f 或 --force 选项强制删除镜像,但这可能会导致正在运行的容器出现问题。
<none> <none> 74f8a2e3ef18 4 days ago 708MB
删除悬空标签REPOSITORY和TAG都是<none>的镜像
docker image prune可以改的正式点musicparty-backend ,实际生成的mpmp
查看本地镜像docker images
root@RainYun-vUQMSsZ0:~/data/docker_data/MusicP# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mpmp latest 846cc72094b5 3 minutes ago 1.23GB
limeve/musicparty-qqmusicapi latest baf6ed79e644 6 weeks ago 383MB
limeve/musicparty-backend latest 58e8cf9d722c 6 weeks ago 1.23GB
limeve/musicparty-neteaseapi latest 37b3535762ab 6 weeks ago 279MB
limeve/musicparty-frontend latest ae5f0bbb85e5 6 weeks ago 705MB
ghcr.io/mhsanaei/3x-ui latest c19a7d905a1f 2 months ago 206MB
halohub/halo 2.19.3 9bf980655048 4 months ago 411MB
snowdreamtech/frps latest 60a3451461c8 4 months ago 33.2MB
vectorim/element-web latest 2fb6f3356553 5 months ago 75.8MB
matrixdotorg/synapse latest f9b69fb10ce7 5 months ago 426MB
halohub/halo 2 6b205c032662 5 months ago 411MB
mysql 8.0 7cd8c3640577 6 months ago 573MB
jc21/nginx-proxy-manager latest 28147ecda659 6 months ago 1.09GB
rustdesk/rustdesk-server latest 5e9792e3a4c0 8 months ago 23.5MB
dko0/lsky-pro latest c89f58b471b2 2 years ago 838MB
mysql 8.0.31 7484689f290f 2 years ago 538MB
修改docker-compose.yml 的 backend: 的 镜像
version: "3.9"
services:
neteaseapi:
image: limeve/musicparty-neteaseapi:latest
expose:
- 3000
qqmusicapi:
image: limeve/musicparty-qqmusicapi:latest
expose:
- 3300
frontend:
image: limeve/musicparty-frontend:latest
expose:
- 3000
backend:
image: mpmp:latest # 或者仅仅是 mpmp,取决于你本地镜像的标签
现状
现在可登录b站、网易云,同时可正常选歌曲,并且有了音量控件
可能需要的功能:
强制切换下一首歌
删除列表中的某一首歌
点歌界面配置一下复选框与复选框全选框
一键添加整个歌单,需要弹窗说明有多少歌曲同时再次确认
增加点歌历史(或者叫播放历史)
感觉反应有点慢,或许可以用更快的架构和语言进行重构?此项目就当做音乐接口的启蒙教程
后端
改完重构docker镜像后,B站可以登录了,可以正常获取收藏夹,但是无法点歌,还需要进一步的查看相关代码进行修改,45行添加完成后就可用了,现在可登录b站,网易云,同时可正常选歌曲
前端
增加了音量控件,不然声音太大了,每次去调电脑的音量合成器有点麻烦
前端用的是nextjs,但是docker镜像的制作不用考虑这些
前端是nextjs
https://github.com/vercel/next.js/
安装,不用安装,music-party\node_modules目录下已经有react与next了,这是项目所有者已经构建过的配置
npm install -g next
先安装环境,项目已经有了,不用子再下载了
npm install
构建所用代码
npm run build
不要用下面这句,会报错,真是莫名其妙,耽误了好久,气死,或许是要用music-party\node_modules目录下的那个版本next
//next buildnpm start与npm run dev的区别
npm start 和 npm run dev 都是用来启动开发或生产环境的脚本命令,但它们的目的和执行方式有所不同。下面是它们的区别和可能导致你遇到的问题的原因。
1. npm run dev
指定端口启动
npm run dev -- --port=3456若无效,则修改文件package.json
"scripts": {
"dev": "next dev -p 3456",用途:
npm run dev通常用于开发环境,启动开发服务器,支持热更新和快速的开发迭代。执行内容:通常会在
package.json文件的scripts字段中定义为类似于:"scripts": { "dev": "next dev" }这条命令会启动 Next.js 的开发服务器,监听文件更改并自动刷新页面。
特点:开发模式下,服务器会自动启用热重载(Hot Reloading),并且通常不会对构建和性能进行优化,目的是便于开发时快速查看效果。
2. npm start
用途:
npm start通常用于启动生产环境的服务器,它一般用于部署时运行已经构建好的项目。执行内容:它通常会在
package.json文件的scripts字段中定义为:"scripts": { "start": "next start" }这个命令会启动 Next.js 的生产服务器,假设你已经通过
next build构建了生产环境的应用。特点:生产模式下,Next.js 会优化项目并启用服务器端渲染(SSR)等功能,以提高性能。它不会启用热重载,且仅在构建后运行。
3. 为什么 npm run dev 可以启动,而 npm start 报错?
这个错误的原因通常是因为在运行 npm start 之前没有先执行 next build 构建应用。在生产环境中,next start 需要 .next 目录来运行应用,而该目录是在 next build 时生成的。
当你运行 npm run dev 时,开发服务器不需要构建文件,它会直接从源码运行,因此不会遇到缺少 .next 目录的问题。
解决办法:
运行
next build:如果你想在生产环境中使用npm start启动应用,首先需要运行next build来构建项目。next build然后再运行
npm start:npm start
这样,npm start 就会启动生产环境的服务器,且不会再报错。
总结:
npm run dev用于开发环境,自动启动开发服务器并支持热更新。npm start用于生产环境,需要先执行next build生成生产环境的构建文件。你需要确保在运行
npm start之前运行next build,否则会出现缺少构建文件的错误。
next build 具体应该怎么使用
next build 是 Next.js 用于构建生产环境应用的命令。它会根据你的项目源代码生成优化后的应用,包含静态文件和服务端渲染的页面。next build 会在 .next 目录下生成构建产物,这是生产环境运行的必要文件。
使用步骤
确保项目已安装依赖: 在执行
next build之前,确保你的项目依赖已经安装好。如果没有安装依赖,可以先执行:npm install运行
next build构建项目: 在项目根目录下运行next build命令。该命令会根据next.config.js的配置、页面、组件等源代码,生成用于生产环境的构建文件。next build或者如果你使用的是
npm,可以通过npm run build来执行:npm run build构建完成后,会在项目根目录下生成
.next文件夹,里面包含了所有构建产物。查看构建日志: 执行
next build时,控制台会输出构建过程中的信息,包括生成的页面、静态文件、构建优化情况等。如果构建中有任何错误,它也会显示详细信息,帮助你调试。构建完成后运行生产服务器: 构建完成后,你可以启动 Next.js 的生产环境服务器,使用以下命令:
next start或者:
npm start这会启动一个生产环境服务器,通常是运行在
http://localhost:3000上,且不会启用开发时的热重载功能。
构建过程中的注意事项
环境变量:
确保你在构建时使用了正确的环境变量。如果你在开发过程中使用了
.env.local或其他环境变量配置,生产环境的构建可能会用不同的环境变量文件。你可以根据需要手动设置环境变量。你可以通过
next build时传递环境变量,或者通过.env.production来管理生产环境的环境变量。
分析构建输出:
Next.js 会输出一些构建优化的分析数据,帮助你了解应用的打包情况。例如,你可能会看到页面拆分的大小、静态资源的大小、以及是否有过多的 JavaScript 被打包等信息。
可以通过
next build后加上--profile来查看详细的构建性能分析:next build --profile
部署前的检查:
在生产构建完成后,可以先在本地运行
next start来检查应用是否正常工作。确保在开发模式下没有出现的问题不会出现在生产环境中。
构建产物:
.next文件夹包含了生产环境所需的构建文件,包括:static:静态文件,如图片、字体等。server:用于服务端渲染的页面和 API 路由。cache:缓存的构建信息,用于加速构建过程。
常见问题
构建失败: 如果你在运行
next build时遇到错误,通常是因为代码中存在问题(例如,语法错误或模块导入问题)。你可以根据错误信息来定位并修复问题。构建时间长: 对于较大的项目,
next build可能会花费一些时间。可以通过代码分割、优化图片等方式来减少构建时间。
总结
构建项目: 运行
next build(或npm run build)来生成生产环境构建文件。运行生产服务器: 使用
next start启动生产环境服务器。检查构建和部署: 在构建后运行应用并确保一切正常,特别是在生产环境下测试应用的表现。
通过以上步骤,你可以成功构建和部署你的 Next.js 应用。
windows版部署启动
网页一起听歌 Windows服务器部署教程_哔哩哔哩_bilibili
可能需要修改的部分
01先前修改相关
51行的函数相关,登录基本信息
91行的函数相关,视频流URL
02合集
还有视频合集的种类又丰富了很多,这个音趴项目毕竟是两年前的,已经很久没维护了
添加的功能对文件的改动
对前端docker镜像的制作
docker build -t musicparty-frontend:latest --file Dockerfile-frontend .删除列表音乐
改动的文件有两个
musicqueue.tsx
C:\Dev\temp\MusicParty\music-party\src\components\musicqueue.tsx
index.tsx
C:\Dev\temp\MusicParty\music-party\pages\index.tsx
改动后的文件
musicqueue.tsx
import { TriangleUpIcon, DeleteIcon } from '@chakra-ui/icons';
import {
Text,
Card,
CardHeader,
Heading,
CardBody,
OrderedList,
ListItem,
Box,
Highlight,
Flex,
Tooltip,
IconButton,
useToast,
} from '@chakra-ui/react';
import { MusicOrderAction } from '../api/musichub';
export const MusicQueue = (props: {
queue: MusicOrderAction[];
top: (actionId: string) => void;
remove: (actionId: string) => void; // 添加 remove 函数
}) => {
const t = useToast();
const handleRemove = async (actionId: string) => {
try {
await props.remove(actionId);
toastInfo(t, `歌曲已从队列中删除`);
} catch (err) {
toastError(t, `删除歌曲失败`);
console.error(err);
}
};
return (
<Card mt={4}>
<CardHeader>
<Heading size={'lg'}>播放队列</Heading>
</CardHeader>
<CardBody>
<OrderedList>
{props.queue.length > 0 ? (
props.queue.map((v) => (
<ListItem key={v.actionId} fontSize={'lg'}>
<Flex alignItems="center">
<Box flex={1}>
{v.music.name} - {v.music.artists}
<Text fontSize={'sm'} fontStyle={'italic'}>
由 {v.enqueuerName} 点歌
</Text>
</Box>
<Flex>
<Tooltip hasArrow label={'将此歌曲至于队列顶端'}>
<IconButton
onClick={() => props.top(v.actionId)}
aria-label={'置顶'}
icon={<TriangleUpIcon />}
mr={2}
/>
</Tooltip>
<Tooltip hasArrow label={'从队列中删除此歌曲'}>
<IconButton
onClick={() => handleRemove(v.actionId)}
aria-label={'删除'}
icon={<DeleteIcon />}
colorScheme="red"
/>
</Tooltip>
</Flex>
</Flex>
</ListItem>
))
) : (
<Text size={'md'}>
<Highlight
query={'点歌'}
styles={{ px: '2', py: '1', rounded: 'full', bg: 'teal.100' }}
>
播放队列为空,请随意点歌吧~
</Highlight>
</Text>
)}
</OrderedList>
</CardBody>
</Card>
);
};
index.tsx
import React, { useEffect, useRef, useState } from "react";
import Head from 'next/head';
import { Connection, Music, MusicOrderAction } from '../src/api/musichub';
import {
Text,
Button,
Card,
CardBody,
CardHeader,
Grid,
GridItem,
Heading,
Input,
ListItem,
OrderedList,
Tab,
TabList,
TabPanel,
TabPanels,
Tabs,
useToast,
Stack,
Popover,
PopoverArrow,
PopoverBody,
PopoverCloseButton,
PopoverContent,
PopoverFooter,
PopoverHeader,
PopoverTrigger,
Portal,
UnorderedList,
Flex,
Highlight,
Box,
} from '@chakra-ui/react';
import { MusicPlayer } from '../src/components/musicplayer';
import { getMusicApis, getProfile } from '../src/api/api';
import { NeteaseBinder } from '../src/components/neteasebinder';
import { MyPlaylist } from '../src/components/myplaylist';
import { toastEnqueueOk, toastError, toastInfo } from '../src/utils/toast';
import { MusicSelector } from '../src/components/musicselector';
import { QQMusicBinder } from '../src/components/qqmusicbinder';
import { MusicQueue } from '../src/components/musicqueue'; // 确保正确导入
import { BilibiliBinder } from '../src/components/bilibilibinder';
export default function Home() {
const [src, setSrc] = useState('');
const [playtime, setPlaytime] = useState(0);
const [nowPlaying, setNowPlaying] = useState<{
music: Music;
enqueuer: string;
}>();
const [queue, setQueue] = useState<MusicOrderAction[]>([]);
const [userName, setUserName] = useState('');
const [newName, setNewName] = useState('');
const [onlineUsers, setOnlineUsers] = useState<
{ id: string; name: string }[]
>([]);
const [inited, setInited] = useState(false);
const [chatContent, setChatContent] = useState<
{ name: string; content: string }[]
>([]);
const [chatToSend, setChatToSend] = useState('');
const [apis, setApis] = useState<string[]>([]);
const t = useToast();
const conn = useRef<Connection>();
useEffect(() => {
if (!conn.current) {
conn.current = new Connection(
`${window.location.origin}/music`,
async (music: Music, enqueuerName: string, playedTime: number) => {
console.log(music);
setSrc(music.url);
setNowPlaying({ music, enqueuer: enqueuerName });
setPlaytime(playedTime);
},
async (actionId: string, music: Music, enqueuerName: string) => {
setQueue((q) => q.concat({ actionId, music, enqueuerName }));
},
async () => {
setQueue((q) => q.slice(1));
},
async (actionId: string, operatorName: string) => {
setQueue((q) => {
const target = q.find((x) => x.actionId === actionId)!;
toastInfo(
t,
`歌曲 "${target.music.name}-${target.music.artists}" 被 ${operatorName} 置顶了`
);
return [target].concat(q.filter((x) => x.actionId !== actionId));
});
},
async (operatorName: string, _) => {
toastInfo(t, `${operatorName} 切到了下一首歌`);
},
async (id: string, name: string) => {
setOnlineUsers((u) => u.concat({ id, name }));
},
async (id: string) => {
setOnlineUsers((u) => u.filter((x) => x.id !== id));
},
async (id: string, newName: string) => {
setOnlineUsers((u) =>
u.map((x) => (x.id === id ? { id, name: newName } : x))
);
},
async (name: string, content: string) => {
setChatContent((c) => c.concat({ name, content }));
},
async (content: string) => {
// todo
console.log(content);
},
async (msg: string) => {
console.error(msg);
toastError(t, msg);
}
);
conn.current
.start()
.then(async () => {
try {
const queue = await conn.current!.getMusicQueue();
setQueue(queue);
const users = await conn.current!.getOnlineUsers();
setOnlineUsers(users);
} catch (err: any) {
toastError(t, err);
}
})
.catch((e) => {
console.error(e);
toastError(t, '请刷新页面重试');
});
getProfile()
.then((u) => {
setUserName(u.name);
})
.catch((e) => {
console.log(e);
toastError(t, '请刷新页面重试');
});
getMusicApis().then((as) => setApis(as));
setInited(true);
}
}, []);
const handleRemove = async (actionId: string) => {
try {
await conn.current!.removeMusic(actionId);
setQueue((q) => q.filter((x) => x.actionId !== actionId));
toastInfo(t, `歌曲已从队列中删除`);
} catch (err) {
toastError(t, `删除歌曲失败`);
console.error(err);
}
};
return (
<Grid templateAreas={`"nav main"`} gridTemplateColumns={'2fr 5fr'} gap='1'>
<Head>
<title>🎵 音趴 🎵</title>
<meta name='description' content='享受音趴!' />
<link rel='icon' href='/favicon.ico' />
<meta name='referrer' content='never' />
</Head>
<GridItem area={'nav'}>
<Stack m={4} spacing={4}>
<Card>
<CardHeader>
<Heading>{`欢迎, ${userName}!`}</Heading>
</CardHeader>
<CardBody>
<Stack>
<Popover>
{({ onClose }) => (
<>
<PopoverTrigger>
<Button>修改名字</Button>
</PopoverTrigger>
<Portal>
<PopoverContent>
<PopoverArrow />
<PopoverHeader>修改名字</PopoverHeader>
<PopoverCloseButton />
<PopoverBody>
<Input
value={newName}
placeholder={'输入新名字'}
onChange={(e) => setNewName(e.target.value)}
></Input>
</PopoverBody>
<PopoverFooter>
<Button
colorScheme='blue'
onClick={async () => {
if (newName === '') return;
await conn.current!.rename(newName);
const user = await getProfile();
setUserName(user.name);
onClose();
setNewName('');
}}
>
确认
</Button>
</PopoverFooter>
</PopoverContent>
</Portal>
</>
)}
</Popover>
{apis.includes('NeteaseCloudMusic') && <NeteaseBinder />}
{apis.includes('QQMusic') && <QQMusicBinder />}
{apis.includes('Bilibili') && <BilibiliBinder />}
</Stack>
</CardBody>
</Card>
<Card>
<CardHeader>
<Heading>在线</Heading>
</CardHeader>
<CardBody>
<UnorderedList>
{onlineUsers.map((u) => {
return <ListItem key={u.id}>{u.name}</ListItem>;
})}
</UnorderedList>
</CardBody>
</Card>
<Card>
<CardHeader>
<Heading>聊天</Heading>
</CardHeader>
<CardBody>
<Flex>
<Input
flex={1}
value={chatToSend}
onChange={(e) => setChatToSend(e.target.value)}
onKeyDown={async (e) => {
if (e.key === "Enter") {
if (chatToSend === '') return;
await conn.current?.chatSay(chatToSend);
setChatToSend('');
}
}}
/>
<Button
ml={2}
onClick={async () => {
if (chatToSend === '') return;
await conn.current?.chatSay(chatToSend);
setChatToSend('');
}}
>
发送
</Button>
</Flex>
<UnorderedList>
{chatContent.map((s) => (
<ListItem key={Math.random() * 1000}>
{`${s.name}: ${s.content}`}
</ListItem>
))}
</UnorderedList>
</CardBody>
</Card>
</Stack>
</GridItem>
<GridItem area={'main'}>
<Tabs>
<TabList>
<Tab>播放列表</Tab>
<Tab>从音乐ID点歌</Tab>
<Tab>从歌单点歌</Tab>
</TabList>
<TabPanels>
<TabPanel>
<Flex flexDirection={'row'} mb={4} alignItems={'flex-end'}>
{nowPlaying ? (
<>
<Heading>
{`正在播放:\n ${nowPlaying?.music.name} - ${nowPlaying?.music.artists}`}
</Heading>
<Text size={'md'} fontStyle={'italic'} ml={2}>
{`由 ${nowPlaying?.enqueuer} 点歌`}
</Text>
</>
) : (
<Heading>暂无歌曲正在播放</Heading>
)}
</Flex>
<MusicPlayer
src={src}
playtime={playtime}
nextClick={() => {
conn.current?.nextSong();
}}
reset={() => {
console.log('reset');
conn.current!.requestSetNowPlaying();
conn.current!.getMusicQueue().then((q) => {
setQueue(q);
});
}}
/>
<MusicQueue
queue={queue}
top={(actionId) => {
conn.current!.topSong(actionId);
}}
remove={handleRemove} // 添加 remove 函数
/>
</TabPanel>
<TabPanel>
<MusicSelector apis={apis} conn={conn.current!} />
</TabPanel>
<TabPanel>
{!inited ? (
<Text>初始化...</Text>
) : (
<MyPlaylist
apis={apis}
enqueue={(id, apiName) => {
conn
.current!.enqueueMusic(id, apiName)
.then(() => {
toastEnqueueOk(t);
})
.catch(() => {
toastError(t, `音乐 {id: ${id}} 加入队列失败`);
});
}}
/>
)}
</TabPanel>
</TabPanels>
</Tabs>
</GridItem>
</Grid>
);
}
结果报错
=> ERROR [5/6] RUN pnpm build 11.9s
------
> [5/6] RUN pnpm build:
1.937 1.937 > music-party@0.1.0 build /
1.937 > next build
1.937
2.369 (node:18) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
2.369 (Use `node --trace-deprecation ...` to show where the warning was created)
2.557 Attention: Next.js now collects completely anonymous telemetry regarding usage.
2.559 This information is used to shape Next.js' roadmap and prioritize features.
2.559 You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
2.559 https://nextjs.org/telemetry
2.559
2.715 info - Linting and checking validity of types...
11.58 Failed to compile.
11.58
11.58 ./pages/index.tsx:155:27
11.58 Type error: Property 'removeMusic' does not exist on type 'Connection'.
11.58
11.58 153 | const handleRemove = async (actionId: string) => {
11.58 154 | try {
11.58 > 155 | await conn.current!.removeMusic(actionId);
11.58 | ^
11.58 156 | setQueue((q) => q.filter((x) => x.actionId !== actionId));
11.58 157 | toastInfo(t, `歌曲已从队列中删除`);
11.58 158 | } catch (err) {
11.66 ELIFECYCLE Command failed with exit code 1.
------
Dockerfile-frontend:6
--------------------
4 | RUN npm install -g pnpm
5 | RUN pnpm install
6 | >>> RUN pnpm build
7 | ENTRYPOINT [ "pnpm" , "start" ]
8 |
--------------------
ERROR: failed to solve: process "/bin/sh -c pnpm build" did not complete successfully: exit code: 1添加音量控件
改动的文件有一个
musicplayer.tsx
C:\Dev\temp\MusicParty\music-party\src\components\musicplayer.tsx
改动后的文件内容
musicplayer.tsx
import {
Button,
Flex,
Icon,
IconButton,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Progress,
Slider,
SliderTrack,
SliderFilledTrack,
SliderThumb,
Text,
Tooltip,
useDisclosure,
useToast,
} from "@chakra-ui/react";
import { ArrowRightIcon } from "@chakra-ui/icons";
import React, { useEffect, useRef, useState } from "react";
export const MusicPlayer = (props: {
src: string;
playtime: number;
nextClick: () => void;
reset: () => void;
}) => {
const audio = useRef<HTMLAudioElement>();
const [length, setLength] = useState(100);
const [time, setTime] = useState(0);
const [volume, setVolume] = useState(100); // 音量状态,范围是 0 到 100
const t = useToast();
const { isOpen, onOpen, onClose } = useDisclosure();
useEffect(() => {
if (!audio.current) {
audio.current = new Audio();
audio.current.addEventListener("durationchange", () => {
setLength(audio.current!.duration);
});
audio.current.addEventListener("timeupdate", () => {
setTime(audio.current!.currentTime);
});
}
if (props.src === "") return;
audio.current.src = props.src;
if (props.playtime !== 0) audio.current.currentTime = props.playtime;
audio.current.play().catch((e: DOMException) => {
if (
e.message ===
"The play() request was interrupted because the media was removed from the document."
)
return;
console.log(e);
onOpen();
});
}, [props.src, props.playtime]);
useEffect(() => {
if (audio.current) {
// 音量值从 0 到 100,映射到 0 到 1 的范围
audio.current.volume = volume / 100;
}
}, [volume]);
return (
<>
<Flex flexDirection={"row"} alignItems={"center"}>
<Progress flex={12} height={"32px"} max={length} value={time} />
<Text flex={2} textAlign={"center"}>{`${Math.floor(
time
)} / ${Math.floor(length)}`}</Text>
<Tooltip hasArrow label="当音乐没有自动播放时,点我试试">
<IconButton
flex={1}
aria-label={"Play"}
mr={2}
icon={
<Icon viewBox="0 0 1024 1024">
<path
d="M128 138.666667c0-47.232 33.322667-66.666667 74.176-43.562667l663.146667 374.954667c40.96 23.168 40.853333 60.8 0 83.882666L202.176 928.896C161.216 952.064 128 932.565333 128 885.333333v-746.666666z"
fill="#3D3D3D"
p-id="2949"
></path>
</Icon>
}
onClick={() => {
audio.current?.play();
audio.current?.pause();
props.reset();
}}
/>
</Tooltip>
<Tooltip hasArrow label={"切歌"}>
<IconButton
flex={1}
icon={<ArrowRightIcon />}
aria-label={"切歌"}
onClick={props.nextClick}
/>
</Tooltip>
</Flex>
{/* 音量控制 */}
<Flex alignItems="center" justifyContent="center" mt={4}>
<Text mr={2}>音量</Text>
<Slider
flex={1}
aria-label="音量"
value={volume}
onChange={(val) => setVolume(val)}
min={0}
max={100}
>
<SliderTrack>
<SliderFilledTrack />
</SliderTrack>
<SliderThumb />
</Slider>
<Text ml={2}>{volume}%</Text>
</Flex>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay>
<ModalContent>
<ModalHeader fontSize={"lg"} fontWeight={"bold"}>
Error
</ModalHeader>
<ModalBody>
<Text>
看起来你的浏览器不允许音频自动播放, 请点击下方的按钮来启用自动播放~
</Text>
</ModalBody>
<ModalFooter>
<Button
colorScheme={"blue"}
onClick={() => {
audio.current?.play();
props.reset();
onClose();
}}
>
启用自动播放
</Button>
</ModalFooter>
</ModalContent>
</ModalOverlay>
</Modal>
</>
);
};
问题
现在是构建遇到了问题
解决
npm run build
不要用下面这句,会报错,真是莫名其妙,耽误了好久,气死了
//next build