update(AudioPlayer): 音频播放逻辑

mack-mac
mackt 7 months ago
parent 5846c05e08
commit 5c68eb8322

@ -10,6 +10,7 @@ import styles from '../index.module.css';
import { SongCardList, Pagination } from '@/components'; import { SongCardList, Pagination } from '@/components';
import { apiGetSongCollect } from '@/services'; import { apiGetSongCollect } from '@/services';
import useAudioStore from '@/store/audio';
import useUserStore from '@/store/user'; import useUserStore from '@/store/user';
export default function Journal() { export default function Journal() {
@ -19,6 +20,13 @@ export default function Journal() {
})), })),
); );
const { setPlayList, setAudioId } = useAudioStore(
useShallow((state) => ({
setAudioId: state.setAudioId,
setPlayList: state.setPlayList,
})),
);
const [type, setType] = useState<'single' | 'journal'>('single'); // 收藏类型 const [type, setType] = useState<'single' | 'journal'>('single'); // 收藏类型
const [total, setTotal] = useState<number>(0); const [total, setTotal] = useState<number>(0);
const [pageNum, setPageNum] = useState<number>(1); const [pageNum, setPageNum] = useState<number>(1);
@ -36,6 +44,13 @@ export default function Journal() {
setType(type); setType(type);
}; };
// 播放
const handlePlay = (id: string) => {
if (!id) return;
setPlayList(list);
setAudioId(id);
};
const getList = async () => { const getList = async () => {
setLoading(true); setLoading(true);
const result = await apiGetSongCollect({ userId: userInfo?.id, pageNum: -1, pageSize: -1 }); const result = await apiGetSongCollect({ userId: userInfo?.id, pageNum: -1, pageSize: -1 });
@ -77,11 +92,20 @@ export default function Journal() {
</li> </li>
))} ))}
</ul> </ul>
<div className="w-[105px] h-[36px] mt-[50px] pl-[45px] text-base text-[15px] leading-[36px] bg-[url(/img/icon/play-ellipse.svg)] bg-no-repeat bg-left hover:text-brand cursor-pointer"> {/* 播放按钮 */}
<div
className="w-[105px] h-[36px] mt-[50px] pl-[45px] text-base text-[15px] leading-[36px] bg-[url(/img/icon/play-ellipse.svg)] bg-no-repeat bg-left hover:text-brand cursor-pointer"
onClick={() => handlePlay(list?.[0]?.id)}
>
</div> </div>
{/* List */} {/* List */}
<SongCardList className="w-full mt-11px mb-[56px]" songList={displayList} listId="mylist" /> <SongCardList
className="w-full mt-11px mb-[56px]"
songList={displayList}
listId="mylist"
collectType="playing"
/>
{!!total && ( {!!total && (
<Pagination <Pagination

@ -18,13 +18,15 @@ export default function AudioPlayer({
className?: string; className?: string;
onSwitchShowCard: () => void; onSwitchShowCard: () => void;
}) { }) {
const { audioId, order, playQueue, showCard, switchSongByDiff, setOrder } = useAudioStore( const { playState, audioId, order, playQueue, showCard, setPlayState, switchSongByDiff, setOrder } = useAudioStore(
useShallow((state) => { useShallow((state) => {
return { return {
playState: state.playState,
audioId: state.audioId, audioId: state.audioId,
order: state.order, order: state.order,
playQueue: state.playQueue, playQueue: state.playQueue,
showCard: state.showCard, showCard: state.showCard,
setPlayState: state.setPlayState,
setOrder: state.setOrder, setOrder: state.setOrder,
switchSongByDiff: state.switchSongByDiff, switchSongByDiff: state.switchSongByDiff,
}; };
@ -35,7 +37,6 @@ export default function AudioPlayer({
const audioRef = useRef(new Audio(audio?.src || '')); const audioRef = useRef(new Audio(audio?.src || ''));
const isReady = useRef<boolean>(false); // 是否加载过组件 const isReady = useRef<boolean>(false); // 是否加载过组件
const [isPlaying, setIsPlaying] = useState(false); // 播放状态
const [trackProgress, setTrackProgress] = useState<number>(0); // 音频进度(s) const [trackProgress, setTrackProgress] = useState<number>(0); // 音频进度(s)
const [duration, setDuration] = useState<number>(0); // 音频总时长(s) const [duration, setDuration] = useState<number>(0); // 音频总时长(s)
@ -49,7 +50,7 @@ export default function AudioPlayer({
// 播放/暂停 // 播放/暂停
const handlePlay = () => { const handlePlay = () => {
setIsPlaying((isPlaying) => !isPlaying); setPlayState(!playState);
}; };
/** /**
@ -59,7 +60,7 @@ export default function AudioPlayer({
const handleSwitchAudio = (direction: number) => { const handleSwitchAudio = (direction: number) => {
if (order === 'single') { if (order === 'single') {
audioRef.current.currentTime = 0; audioRef.current.currentTime = 0;
!isPlaying && setIsPlaying(true); !playState && setPlayState(true);
return; return;
} }
switchSongByDiff(direction); switchSongByDiff(direction);
@ -69,7 +70,7 @@ export default function AudioPlayer({
const handleChangeProgress = (value: string) => { const handleChangeProgress = (value: string) => {
audioRef.current.currentTime = Number(value); audioRef.current.currentTime = Number(value);
setTrackProgress(audioRef.current.currentTime); setTrackProgress(audioRef.current.currentTime);
setIsPlaying(true); setPlayState(true);
}; };
// 监听音频进度 // 监听音频进度
@ -85,8 +86,8 @@ export default function AudioPlayer({
// 播放/暂停事件 // 播放/暂停事件
useEffect(() => { useEffect(() => {
isPlaying ? audioRef.current.play() : audioRef.current.pause(); playState ? audioRef.current.play() : audioRef.current.pause();
}, [isPlaying]); }, [playState]);
// 切换歌曲 // 切换歌曲
useEffect(() => { useEffect(() => {
@ -101,7 +102,7 @@ export default function AudioPlayer({
if (isReady.current) { if (isReady.current) {
audioRef.current.addEventListener('loadedmetadata', handleLoadedMetadata); // 获取音频时长 audioRef.current.addEventListener('loadedmetadata', handleLoadedMetadata); // 获取音频时长
audioRef.current.play(); audioRef.current.play();
setIsPlaying(true); setPlayState(true);
} else { } else {
isReady.current = true; isReady.current = true;
} }
@ -119,7 +120,7 @@ export default function AudioPlayer({
}; };
document.addEventListener('keydown', handleKeyDown); // 监听快捷键 document.addEventListener('keydown', handleKeyDown); // 监听快捷键
setIsPlaying(false); // 禁止自动播放 setPlayState(false); // 禁止自动播放
return () => { return () => {
audioRef.current.pause(); audioRef.current.pause();
@ -130,7 +131,7 @@ export default function AudioPlayer({
return ( return (
<PlayerControl <PlayerControl
playStatus={isPlaying} playStatus={playState}
order={order} order={order}
audio={audio} audio={audio}
duration={duration} duration={duration}

@ -0,0 +1,13 @@
// 均衡器效果
import styles from './index.module.css';
export default function Equalizer({ active }: { active: boolean }) {
return (
<div className={`${styles.equalizer} ${!active && styles.paused}`}>
<span></span>
<span></span>
<span></span>
</div>
);
}

@ -0,0 +1,73 @@
.equalizer {
width: 24px;
height: 24px;
padding: 6px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
}
.equalizer span {
width: 2px;
border-radius: 4px;
background: #c43737ff;
animation: up-and-down 2s linear infinite;
}
.equalizer span:nth-child(1) {
animation-delay: 0.4s;
}
.equalizer span:nth-child(2) {
}
.equalizer span:nth-child(3) {
animation-delay: 0.7s;
}
.equalizer.paused span {
animation-play-state: paused;
}
@keyframes up-and-down {
0%,
100% {
clip-path: inset(27% 0 0 0);
}
10% {
clip-path: inset(17% 0 0 0);
}
20% {
clip-path: inset(55% 0 0 0);
}
30% {
clip-path: inset(30% 0 0 0);
}
40% {
clip-path: inset(13% 0 0 0);
}
50% {
clip-path: inset(38% 0 0 0);
}
60% {
clip-path: inset(80% 0 0 0);
}
70% {
clip-path: inset(21% 0 0 0);
}
80% {
clip-path: inset(0% 0 0 0);
}
90% {
clip-path: inset(36% 0 0 0);
}
}

@ -1,43 +0,0 @@
/**
*
*/
'use client';
import { useShallow } from 'zustand/react/shallow';
import { SongCard } from '@/components';
import useAudioStore from '@/store/audio';
interface Props {
listId: string;
songList: SongInfo[];
className?: string;
}
export default function JournalItem({ listId, songList, className }: Props) {
const { setPlayList, setAudioId, playListId } = useAudioStore(
useShallow((state) => ({
setAudioId: state.setAudioId,
setPlayList: state.setPlayList,
playListId: state.playListId,
})),
);
const handlePlayList = (id: string) => {
// 正在播放其他歌单
if (playListId !== listId) {
setPlayList({ id: listId, list: songList });
}
// 正在播放当前歌单
setAudioId(id);
};
return (
<div className={`${className}`}>
{songList.map((song: SongInfo) => (
<SongCard key={song.id} {...song} onPlay={(audioId: string) => handlePlayList(audioId)} />
))}
</div>
);
}

@ -1,16 +1,29 @@
import Image from 'next/image'; import Image from 'next/image';
import { ButtonCollect } from '@/components'; import { ButtonCollect, Equalizer } from '@/components';
interface Props extends SongInfo {
/** 播放状态 */
playState: boolean;
/** 展示收藏按钮 */
showCollect: boolean;
/** 显示均衡器效果 */
showEq: boolean;
onPlay: (id: string) => void;
}
export default function JournalItem({ export default function JournalItem({
playState,
id, id,
title, title,
pic, pic,
artist, artist,
haveCollect, haveCollect,
duration, duration,
showEq,
showCollect,
onPlay, onPlay,
}: SongInfo & { onPlay: (id: string) => void }) { }: Props) {
return ( return (
<div <div
className="flex flex-row items-center justify-between w-full h-[72px] my-[3px] py-[12px] px-[18px] rounded-[3px] hover:bg-[#f2f3f7] group cursor-pointer" className="flex flex-row items-center justify-between w-full h-[72px] my-[3px] py-[12px] px-[18px] rounded-[3px] hover:bg-[#f2f3f7] group cursor-pointer"
@ -18,7 +31,9 @@ export default function JournalItem({
> >
{/* left */} {/* left */}
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
{/* 专辑封面 */}
<Image width={48} height={48} src={pic} alt={title} className="w-[48px] h-[48px] overflow-hidden" unoptimized /> <Image width={48} height={48} src={pic} alt={title} className="w-[48px] h-[48px] overflow-hidden" unoptimized />
{/* 歌曲名称/歌手 */}
<div className="flex flex-col justify-between ml-[15px]"> <div className="flex flex-col justify-between ml-[15px]">
<div className="text-[15px] leading-[21px] text-base group-hover:text-brand">{title}</div> <div className="text-[15px] leading-[21px] text-base group-hover:text-brand">{title}</div>
<div className="text-[13px] leading-[18.2px] text-[rgba(0,0,0,0.7)] group-hover:text-brand">{artist}</div> <div className="text-[13px] leading-[18.2px] text-[rgba(0,0,0,0.7)] group-hover:text-brand">{artist}</div>
@ -27,22 +42,19 @@ export default function JournalItem({
{/* right */} {/* right */}
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
<Image {/* 均衡器效果 */}
width={24} {showEq && <Equalizer active={playState} />}
height={24} {/* 音频时长 */}
src={'/img/icon/audio-playing.svg'}
alt={title}
className="w-[24px] h-[24px] overflow-hidden"
/>
<p className="ml-[30px] mr-[13px] text-[12px] leading-[16.8px] text-[rgba(0,0,0,0.4)]">{duration || '00:00'}</p> <p className="ml-[30px] mr-[13px] text-[12px] leading-[16.8px] text-[rgba(0,0,0,0.4)]">{duration || '00:00'}</p>
{/* 收藏按钮 */}
<ButtonCollect {showCollect && (
journalId={id} <ButtonCollect
active={haveCollect} journalId={id}
collectType="0" active={haveCollect}
textClassName="w-[42px] ml-[6px] mr-[24px] text-[14px] leading-[16px]" collectType="0"
/> textClassName="w-[42px] ml-[6px] mr-[24px] text-[14px] leading-[16px]"
/>
)}
</div> </div>
</div> </div>
); );

@ -0,0 +1,58 @@
/**
*
*/
'use client';
import { useShallow } from 'zustand/react/shallow';
import { SongCard } from '@/components';
import useAudioStore from '@/store/audio';
interface Props {
listId: string;
songList: SongInfo[];
className?: string;
/**
* always:
* playing:
*/
collectType?: 'always' | 'playing';
}
export default function JournalItem({ songList, className, collectType = 'always' }: Props) {
const { playState, audioId, setPlayState, setAudioId, setPlayList } = useAudioStore(
useShallow((state) => ({
playState: state.playState,
audioId: state.audioId,
setPlayState: state.setPlayState,
setAudioId: state.setAudioId,
setPlayList: state.setPlayList,
})),
);
const handlePlayList = (id: string) => {
if (!id) return;
if (id === audioId) {
setPlayState(!playState);
return;
}
// 设置播放列表、音频id
setPlayList(songList);
setAudioId(id);
};
return (
<div className={`${className}`}>
{songList.map((item: SongInfo) => (
<SongCard
key={item.id}
{...item}
playState={playState}
showEq={item.id === audioId}
onPlay={(audioId: string) => handlePlayList(audioId)}
showCollect={collectType === 'always' || (collectType === 'playing' && item.id === audioId)}
/>
))}
</div>
);
}

@ -2,6 +2,9 @@ export { default as Logo } from './Logo';
export { default as Header } from './Header/index'; export { default as Header } from './Header/index';
export { default as Footer } from './Footer'; export { default as Footer } from './Footer';
// Icon
export { default as Equalizer } from './Icon/Equalizer';
// Home // Home
export { default as Category } from './Category'; export { default as Category } from './Category';
export { default as ContributorCard } from './ContributorCard'; export { default as ContributorCard } from './ContributorCard';
@ -28,9 +31,9 @@ export { default as ButtonCollect } from './ButtonCollect';
export { default as PlayerBar } from './AudioPlayer/PlayerBar'; export { default as PlayerBar } from './AudioPlayer/PlayerBar';
export { default as AudioPlayer } from './AudioPlayer/Player'; export { default as AudioPlayer } from './AudioPlayer/Player';
// Song // SongCard
export { default as SongCard } from './Song/SongCard'; export { default as SongCard } from './SongCard/SongCard';
export { default as SongCardList } from './Song/SongCardList'; export { default as SongCardList } from './SongCard/SongCardList';
// Comment // Comment
export { default as Comment } from './Comment/Comment'; export { default as Comment } from './Comment/Comment';
@ -39,4 +42,4 @@ export { default as CommentForm } from './Comment/CommentForm';
export { default as CommentItem } from './Comment/CommentItem'; export { default as CommentItem } from './Comment/CommentItem';
export { default as CommentList } from './Comment/CommentList'; export { default as CommentList } from './Comment/CommentList';
export { default as PlayerButton } from './AudioPlayer/PlayerButton'; export { default as PlayerButton } from './AudioPlayer/PlayerButton';
export { default as Pagination } from './Pagination/index'; export { default as Pagination } from './Pagination';

@ -4,15 +4,13 @@ import { create } from 'zustand';
import { devtools, persist, createJSONStorage } from 'zustand/middleware'; import { devtools, persist, createJSONStorage } from 'zustand/middleware';
interface AuioState { interface AuioState {
/** 播放状态 */ /** 播放状态 true: 播放 false: 暂停 */
playStatus: boolean; playState: boolean;
/** 播放列表id */ /** 播放顺序 list_loop: 列表循环 random: 随机 single: 单曲 */
playListId: string;
/** 播放顺序 */
order: PlayOrder; order: PlayOrder;
/** 播放器显示状态 */ /** 播放器显示状态 true: 显示 false: 隐藏 */
show: boolean; show: boolean;
/** 单曲卡牌展示 */ /** 单曲卡牌展示 true: 显示 false: 隐藏 */
showCard: boolean; showCard: boolean;
/** 当前音频id */ /** 当前音频id */
audioId: string; audioId: string;
@ -20,6 +18,7 @@ interface AuioState {
playList: SongInfo[]; playList: SongInfo[];
/** 播放队列 */ /** 播放队列 */
playQueue: SongInfo[]; playQueue: SongInfo[];
setPlayState: (state: boolean) => void;
setOrder: (order: PlayOrder) => void; setOrder: (order: PlayOrder) => void;
setAudioId: (id: string) => void; setAudioId: (id: string) => void;
// 显示/隐藏播放器 // 显示/隐藏播放器
@ -27,7 +26,7 @@ interface AuioState {
// 显示/隐藏单曲卡片 // 显示/隐藏单曲卡片
setShowCard: (value: boolean) => void; setShowCard: (value: boolean) => void;
// 设置播放列表 // 设置播放列表
setPlayList: (params: { id: string; list: SongInfo[] }) => void; setPlayList: (list: SongInfo[]) => void;
// 切换歌曲 -1: 上一首 1: 下一首 // 切换歌曲 -1: 上一首 1: 下一首
switchSongByDiff: (diff: number) => void; switchSongByDiff: (diff: number) => void;
} }
@ -37,8 +36,20 @@ const useAuioState = create<AuioState>()(
persist( persist(
(set, get) => { (set, get) => {
const setAudioId = (id: string) => { const setAudioId = (id: string) => {
if (id === get().audioId) return; // 当前歌曲 if (id === get().audioId) {
if (get().playQueue.findIndex((item) => item.id === id) < 0) return; // 不在歌单内 // 传入当前歌曲id 切换播放/暂停状态
// set(
// produce((state) => {
// state.playState = !get().playState;
// }),
// );
return;
}
// 不在歌单内
if (get().playQueue.findIndex((item) => item.id === id) < 0) {
return;
}
set( set(
produce((state) => { produce((state) => {
@ -97,14 +108,19 @@ const useAuioState = create<AuioState>()(
return { return {
audioId: '', audioId: '',
order: 'list_loop', order: 'list_loop',
playStatus: false, playState: false,
show: false, show: false,
showCard: false, showCard: false,
playListId: '',
playList: [], playList: [],
playQueue: [], playQueue: [],
setAudioId, setAudioId,
setOrder, setOrder,
setPlayState: (value) =>
set(
produce((state) => {
state.playState = value;
}),
),
setShow: (value) => setShow: (value) =>
set( set(
produce((state) => { produce((state) => {
@ -117,12 +133,12 @@ const useAuioState = create<AuioState>()(
state.showCard = value; state.showCard = value;
}), }),
), ),
setPlayList: (params) => { setPlayList: (list) => {
set( set(
produce((state) => { produce((state) => {
state.playListId = params.id; state.playList = list;
state.playList = params.list; state.order = 'list_loop';
state.playQueue = params.list; state.playQueue = list;
}), }),
); );
}, },

Loading…
Cancel
Save