feat: 期刊页面播放歌曲

main
fadeaway 6 months ago
parent 6ce2f585d3
commit 59b56124eb

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

@ -4,7 +4,7 @@
import Image from 'next/image';
import ArticalWrap from '@/components/ArticalWrap';
import SongItem from '@/components/SongItem';
import SongList from '@/components/SongList';
import type { Metadata } from 'next';
@ -44,14 +44,6 @@ export default async function Journal({ searchParams: { id } }: any) {
</div>
<section className="relative w-full flex-1 bg-white mt-[-10px] rounded-tl-[12px] rounded-tr-[12px] z-1 py-[17px] px-[18px] mb-[79px]">
<Image
className="w-[60px] h-[60px] absolute top-[-30px] right-[16px]"
width={60}
height={60}
unoptimized
src="/img/icon_pause.png"
alt="icon_pause"
/>
<div className="flex items-center flex-wrap text-[#000000b2] mb-[5px]">
<div className="text-[14px] font-medium leading-[19.6px]">VOL·{journalInfo?.journalNo}</div>
<div className="ml-[11px] flex items-center flex-wrap text-[12px] leading-[12px]">
@ -73,8 +65,7 @@ export default async function Journal({ searchParams: { id } }: any) {
<ArticalWrap content={journalInfo?.content} />
<h5 className="mb-[12px] text-[#000000f2] text-[15px] h-[18px]">{songList?.length}</h5>
{songList?.map((song: ISong) => <SongItem key={song.id} data={song} />)}
<SongList list={songList} />
</section>
<section className="w-full flex items-center border-t-[1px] border-[#00000019] pt-[14px] pb-[35px] px-[18px]">
@ -90,7 +81,11 @@ export default async function Journal({ searchParams: { id } }: any) {
src="/img/icon_comment.png"
alt="icon_comment"
/>
<div className="absolute top-0 left-[100%] text-[8px] text-[#00000066]">{journalInfo?.totalCommentReply}</div>
{journalInfo && journalInfo.totalCommentReplyInt > 0 && (
<div className="absolute top-0 left-[100%] text-[8px] text-[#00000066]">
{journalInfo?.totalCommentReply}
</div>
)}
</div>
</section>
</main>

@ -10,7 +10,10 @@ export interface IAudioPlayerRef {
audio: HTMLAudioElement | null;
}
export default forwardRef(function AudioPlayer({ src }: { src?: string }, ref: React.Ref<IAudioPlayerRef>) {
export default forwardRef(function AudioPlayer(
{ src, autoPlay }: { src?: string; autoPlay?: boolean },
ref: React.Ref<IAudioPlayerRef>,
) {
const [curTime, setCurTime] = useState('00:00');
const [totalTime, setTotalTime] = useState('00:00');
const audioRef = useRef<HTMLAudioElement>(null);
@ -55,8 +58,15 @@ export default forwardRef(function AudioPlayer({ src }: { src?: string }, ref: R
const handleSlideChange = () => requestAnimationFrame(() => setSliderBgSize());
// 初始就开始播放
const srcRef = useRef(src);
srcRef.current = src;
const autoPlayRef = useRef(autoPlay);
autoPlayRef.current = autoPlay;
useEffect(() => {
audioRef?.current?.play();
if (srcRef.current && autoPlayRef.current !== false) {
audioRef?.current?.play();
}
}, []);
useImperativeHandle(
@ -72,7 +82,7 @@ export default forwardRef(function AudioPlayer({ src }: { src?: string }, ref: R
<audio
ref={audioRef}
preload="auto"
autoPlay
autoPlay={autoPlay === false ? false : true}
src={src}
onLoadedMetadata={handleLoadedMetadata}
onTimeUpdate={handleTimeUpdate}

@ -0,0 +1,36 @@
'use client';
import { useEffect } from 'react';
import Image from 'next/image';
import { useJournalAudioContext } from '@/context/JournalAudioContext';
export default function JournalAudioCtrlBtn() {
const { audioPlayer, songList, curSong, setCurSong, playing, setPlaying } = useJournalAudioContext();
const handleClick = () => {
if (curSong === undefined) {
setCurSong?.(songList?.[0]);
}
setPlaying?.(!playing);
};
useEffect(() => {
if (curSong?.src && audioPlayer?.audio) {
playing ? audioPlayer?.audio?.play() : audioPlayer?.audio?.pause();
}
}, [curSong?.src, playing, audioPlayer?.audio]);
return (
<Image
className="w-[60px] h-[60px] absolute top-[-30px] right-[16px]"
width={60}
height={60}
unoptimized
src={`/img/${playing ? 'icon_pause.png' : 'icon_play.png'}`}
alt="play_pause"
onClick={handleClick}
/>
);
}

@ -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: #b44343ff;
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);
}
}

@ -3,12 +3,32 @@
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import styles from './index.module.css';
import { useJournalAudioContext } from '@/context/JournalAudioContext';
export default function SongItem(props: { data: ISong }) {
const { id, pic, title, artist } = props?.data || {};
const song = props?.data || {};
const { id, pic, title, artist, album } = song;
const router = useRouter();
const handleClick = () => {
const { curSong, setCurSong, playing, setPlaying } = useJournalAudioContext();
const activeTextColor = curSong?.id && id && curSong?.id === id ? 'text-[#b44343ff]' : '';
// 点击歌曲封面跳转到单曲页面
const handleCoverClick = () => {
router.push(`/music?id=${id}`);
};
// 点击歌曲信息播放歌曲
const handleSongClick = () => {
setCurSong?.(song);
setPlaying?.(curSong?.id !== id ? true : !playing);
};
// 点击右侧图标,弹出弹窗
const handleBudsClick = () => {};
return (
<div className="flex items-center py-[12px]">
<Image
@ -18,13 +38,33 @@ export default function SongItem(props: { data: ISong }) {
unoptimized
src={pic}
alt="歌曲封面"
onClick={handleClick}
onClick={handleCoverClick}
/>
<div className="flex flex-1 flex-col pl-[15px] pr-[15px] overflow-hidden">
<div className="font-medium text-[15px] leading-[21px] mb-[2px] text-[#000000f2] truncate">{title}</div>
<div className="text-[12px] leading-[16.8px] text-[#000000b2] truncate">{artist}</div>
<div className="flex flex-1 flex-col pl-[15px] pr-[15px] overflow-hidden" onClick={handleSongClick}>
<div className={`font-medium text-[15px] leading-[21px] mb-[2px] text-[#000000f2] truncate ${activeTextColor}`}>
{title}
</div>
<div className={`text-[12px] leading-[16.8px] text-[#000000b2] truncate ${activeTextColor}`}>
{[artist, album].filter((str) => !!str).join(' / ')}
</div>
</div>
<Image className="w-[24px] h-[24px]" width={24} height={24} unoptimized src="/img/icon_song_dot.png" alt="dot" />
{activeTextColor && (
// 均衡器效果
<div className={`${styles.equalizer} ${!playing && styles.paused}`}>
<span></span>
<span></span>
<span></span>
</div>
)}
<Image
className="w-[24px] h-[24px]"
width={24}
height={24}
unoptimized
src="/img/icon_song_dot.png"
alt="dot"
onClick={handleBudsClick}
/>
</div>
);
}

@ -0,0 +1,38 @@
'use client';
import { useState, useRef } from 'react';
import AudioPlayer from '@/components/AudioPlayer';
import type { IAudioPlayerRef } from '@/components/AudioPlayer';
import JournalAudioCtrlBtn from '@/components/JournalAudioCtrlBtn';
import SongItem from '@/components/SongItem';
import { JournalAudioContext } from '@/context/JournalAudioContext';
export default function SongList({ list }: { list: ISong[] }) {
const audioPlayerRef = useRef<IAudioPlayerRef>(null);
const [curSong, setCurSong] = useState<ISong | undefined>();
const [playing, setPlaying] = useState(false);
const providerVal = {
audioPlayer: audioPlayerRef?.current,
songList: list,
curSong,
setCurSong,
playing,
setPlaying,
};
return (
<JournalAudioContext.Provider value={providerVal}>
<JournalAudioCtrlBtn />
<h5 className="mb-[12px] text-[#000000f2] text-[15px] h-[18px]">{list?.length || 0}</h5>
{list?.map((song: ISong) => <SongItem key={song.id} data={song} />)}
<div className="hidden">
<AudioPlayer ref={audioPlayerRef} autoPlay={false} src={curSong?.src} />
</div>
</JournalAudioContext.Provider>
);
}

@ -0,0 +1,21 @@
import { createContext, useContext } from 'react';
import type { IAudioPlayerRef } from '@/components/AudioPlayer';
interface IJournalAudioContext {
audioPlayer: IAudioPlayerRef | null;
songList?: ISong[];
curSong?: ISong;
setCurSong?: any;
playing?: boolean;
setPlaying?: any;
}
const JournalAudioContext = createContext<IJournalAudioContext>({
audioPlayer: null,
});
JournalAudioContext.displayName = 'JournalAudioContext';
export const useJournalAudioContext = () => useContext(JournalAudioContext);
export { JournalAudioContext };
Loading…
Cancel
Save