parent
0510cb0ba3
commit
6ce2f585d3
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.3 KiB |
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 歌曲播放页面
|
||||
*/
|
||||
import Image from 'next/image';
|
||||
|
||||
import EnterQueyueBtn from '@/components/EnterQueyueBtn';
|
||||
import MusicPanel from '@/components/MusicPanel';
|
||||
|
||||
async function getMusic(songId: string) {
|
||||
const res = await fetch(`http://39.103.180.196:9012/luoo-music/song/${songId}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export default async function Music({ searchParams: { id } }: any) {
|
||||
const res = await getMusic(id);
|
||||
const musicInfo = res?.data;
|
||||
return (
|
||||
<main className="max-w-screen-sm min-h-screen mx-auto flex flex-col items-center text-white font-normal bg-gradient-to-b from-[#030303] to-[#1f1f20] px-[18px]">
|
||||
<div className="flex items-center w-full bg-[#1d1d1d] text-[#ffffffb2] text-[14px] py-[12px] px-[18px] rounded-[6px] mt-[4px]">
|
||||
<Image className="w-[28px] h-[28px]" width={28} height={28} src="/img/app_icon_white_bg.svg" alt="queyue" />
|
||||
<span className="flex-1 pl-[12px]">邀你一起来【雀乐】听歌</span>
|
||||
<Image className="w-[24px] h-[24px]" width={24} height={24} src="/img/jourrnal_icon-5.svg" alt="right-arrow" />
|
||||
</div>
|
||||
|
||||
<MusicPanel musicInfo={musicInfo} />
|
||||
|
||||
<div className="mb-[30px]">
|
||||
<EnterQueyueBtn />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
.audioPlayer input[type='range'] {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
-webkit-appearance: none;
|
||||
background:
|
||||
-webkit-linear-gradient(#fff, #fff) no-repeat,
|
||||
#ffffff33;
|
||||
background-size: 0%;
|
||||
position: relative;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.audioPlayerInput::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
background-color: #fff;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
|
||||
import { formatTime } from '@/utils/formatTime';
|
||||
|
||||
import styles from './index.module.css';
|
||||
|
||||
export interface IAudioPlayerRef {
|
||||
audio: HTMLAudioElement | null;
|
||||
}
|
||||
|
||||
export default forwardRef(function AudioPlayer({ src }: { src?: string }, ref: React.Ref<IAudioPlayerRef>) {
|
||||
const [curTime, setCurTime] = useState('00:00');
|
||||
const [totalTime, setTotalTime] = useState('00:00');
|
||||
const audioRef = useRef<HTMLAudioElement>(null);
|
||||
const sliderRef = useRef<HTMLInputElement>(null);
|
||||
const isDragingRef = useRef(false);
|
||||
|
||||
const setSliderBgSize = () => {
|
||||
if (sliderRef?.current) {
|
||||
sliderRef.current.style.backgroundSize = `${(Number(sliderRef.current.value) / (audioRef?.current?.duration || 0)) * 100}%`;
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadedMetadata = () => {
|
||||
setCurTime(formatTime(audioRef?.current?.currentTime));
|
||||
setTotalTime(formatTime(audioRef?.current?.duration));
|
||||
};
|
||||
|
||||
const handleTimeUpdate = () => {
|
||||
requestAnimationFrame(() => {
|
||||
const currentTime = audioRef?.current?.currentTime || 0;
|
||||
if (!isDragingRef?.current) {
|
||||
if (sliderRef?.current) {
|
||||
sliderRef.current.value = `${currentTime}`;
|
||||
setSliderBgSize();
|
||||
}
|
||||
}
|
||||
setCurTime(formatTime(currentTime));
|
||||
setTotalTime(formatTime(audioRef?.current?.duration || 0));
|
||||
});
|
||||
};
|
||||
|
||||
const handleSlideStart = () => {
|
||||
isDragingRef.current = true;
|
||||
};
|
||||
|
||||
const handleSlideEnd = () => {
|
||||
if (audioRef?.current) {
|
||||
audioRef.current.currentTime = Number(sliderRef?.current?.value || 0);
|
||||
}
|
||||
isDragingRef.current = false;
|
||||
};
|
||||
|
||||
const handleSlideChange = () => requestAnimationFrame(() => setSliderBgSize());
|
||||
|
||||
useEffect(() => {
|
||||
audioRef?.current?.play();
|
||||
}, []);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
audio: audioRef.current,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`w-full ${styles.audioPlayer}`}>
|
||||
<audio
|
||||
ref={audioRef}
|
||||
preload="auto"
|
||||
autoPlay
|
||||
src={src}
|
||||
onLoadedMetadata={handleLoadedMetadata}
|
||||
onTimeUpdate={handleTimeUpdate}
|
||||
/>
|
||||
<input
|
||||
ref={sliderRef}
|
||||
className={styles.audioPlayerInput}
|
||||
type="range"
|
||||
step="1"
|
||||
defaultValue="0"
|
||||
min="0"
|
||||
max={`${audioRef?.current?.duration || 0}`}
|
||||
onMouseDown={handleSlideStart}
|
||||
onMouseUp={handleSlideEnd}
|
||||
onTouchStart={handleSlideStart}
|
||||
onTouchEnd={handleSlideEnd}
|
||||
onChange={handleSlideChange}
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-[12px] leading-[16.8px] text-[#ffffff66]">{curTime}</div>
|
||||
<div className="text-[12px] leading-[16.8px] text-[#ffffff66]">{totalTime}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
@ -0,0 +1,25 @@
|
||||
'use client';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
export default function EnterQueyueBtn() {
|
||||
const router = useRouter();
|
||||
const handleEnterQueyue = () => {
|
||||
const result = confirm('离开微信,打开第三方应用');
|
||||
if (result) {
|
||||
alert('打开应用无效跳转下载页');
|
||||
window.location.href = 'queyue://'; // 打开某手机上的某个app应用
|
||||
setTimeout(function () {
|
||||
router.push('/download');
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<button
|
||||
className="w-[283px] h-[44px] flex items-center justify-center bg-[#B44343] font-medium text-[15px] rounded-full"
|
||||
onClick={handleEnterQueyue}
|
||||
>
|
||||
进入【雀乐】
|
||||
</button>
|
||||
);
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
|
||||
import Image from 'next/image';
|
||||
|
||||
import AudioPlayer from '@/components/AudioPlayer';
|
||||
import type { IAudioPlayerRef } from '@/components/AudioPlayer';
|
||||
|
||||
export default function MusicPanel(props: { musicInfo: ISong }) {
|
||||
const { musicInfo } = props || {};
|
||||
const audioPlayerRef = useRef<IAudioPlayerRef>(null);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
|
||||
const togglePlay = () => {
|
||||
setPlaying(!playing);
|
||||
if (playing) {
|
||||
audioPlayerRef.current?.audio?.pause();
|
||||
} else {
|
||||
audioPlayerRef.current?.audio?.play();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setPlaying(!!!audioPlayerRef.current?.audio?.paused);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative w-[100%] aspect-square mt-[12px]">
|
||||
<Image className="rounded-[6px] object-cover" unoptimized fill src={musicInfo?.pic} alt="cover" />
|
||||
{playing ? (
|
||||
<Image
|
||||
className="absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] w-[60px] h-[60px]"
|
||||
width={60}
|
||||
height={60}
|
||||
unoptimized
|
||||
src="/img/icon_pause_w.png"
|
||||
alt="pause"
|
||||
onClick={togglePlay}
|
||||
/>
|
||||
) : (
|
||||
<Image
|
||||
className="absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] w-[60px] h-[60px]"
|
||||
width={60}
|
||||
height={60}
|
||||
unoptimized
|
||||
src="/img/icon_play_w.png"
|
||||
alt="play"
|
||||
onClick={togglePlay}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="w-full mt-[20px]">
|
||||
<div className="text-[12px] h-[22px] w-[fit-content] flex items-center text-white rounded-full px-[8px] py-[0] bg-[#2b2b2c]">
|
||||
VOL {musicInfo?.journalNo}
|
||||
</div>
|
||||
</div>
|
||||
<h5 className="mt-[9px] font-medium text-[20px] leading-[28px] w-full">{musicInfo?.title}</h5>
|
||||
<p className="mt-[3px] text-[12px] text-[#ffffffb2] w-full">{musicInfo?.artist}</p>
|
||||
|
||||
<div className="w-full mt-[18px] mb-[46px]">
|
||||
<AudioPlayer ref={audioPlayerRef} src={musicInfo?.src} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
export const formatTime = (seconds = 0) => {
|
||||
let h: any = Math.floor(seconds / 3600);
|
||||
let m: any = Math.floor((seconds % 3600) / 60);
|
||||
let s: any = Math.floor(seconds % 60);
|
||||
if (h > 0) {
|
||||
h = h < 10 ? '0' + h : h;
|
||||
}
|
||||
m = m < 10 ? '0' + m : m;
|
||||
s = s < 10 ? '0' + s : s;
|
||||
return (h > 0 ? h + ':' : '') + m + ':' + s;
|
||||
};
|
Loading…
Reference in new issue