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