"use client"; import { useState, useRef, useEffect } from "react"; interface AudioPlayerProps { src: string; title?: string; duration?: number; // 从数据库获取的时长 recordingId?: string; // 录音ID,用于检查访问权限 } export default function AudioPlayer({ src, title, duration: dbDuration, recordingId, }: AudioPlayerProps) { const [isPlaying, setIsPlaying] = useState(false); const [isLoading, setIsLoading] = useState(true); const [duration, setDuration] = useState(dbDuration || 0); const [currentTime, setCurrentTime] = useState(0); const [volume, setVolume] = useState(1); const [showVolumeControl, setShowVolumeControl] = useState(false); const [showDownloadMenu, setShowDownloadMenu] = useState(false); const [error, setError] = useState(null); const [audioUrl, setAudioUrl] = useState(src); const audioRef = useRef(null); const progressRef = useRef(null); // 检查 S3 文件访问权限 useEffect(() => { if (recordingId && src.includes("amazonaws.com")) { const checkAccess = async () => { try { const response = await fetch( `/api/recordings/${recordingId}/check-access` ); const data = await response.json(); if (data.accessible) { console.log("S3 文件可访问:", data.url); setError(null); // 使用代理 URL 而不是直接访问 S3 const proxyUrl = `/api/recordings/${recordingId}/stream`; setAudioUrl(proxyUrl); } else { console.error("S3 文件无法访问:", data.error); setError("音频文件无法访问,请检查权限设置"); } } catch (error) { console.error("检查文件访问失败:", error); setError("检查文件访问失败"); } }; checkAccess(); } }, [recordingId, src]); useEffect(() => { const audio = audioRef.current; if (!audio) return; const handleLoadedMetadata = () => { console.log("音频元数据加载完成:", { duration: audio.duration, src: audio.src, readyState: audio.readyState, networkState: audio.networkState, currentSrc: audio.currentSrc, error: audio.error, }); // 如果数据库中没有时长,则从音频文件获取 if (!dbDuration) { setDuration(audio.duration); } setIsLoading(false); setError(null); }; const handleTimeUpdate = () => { setCurrentTime(audio.currentTime); }; const handlePlay = () => { console.log("音频开始播放"); setIsPlaying(true); }; const handlePause = () => { console.log("音频暂停"); setIsPlaying(false); }; const handleEnded = () => { console.log("音频播放结束"); setIsPlaying(false); setCurrentTime(0); }; const handleError = (e: Event) => { const target = e.target as HTMLAudioElement; console.error("音频加载失败:", { error: target.error, errorCode: target.error?.code, errorMessage: target.error?.message, src: target.src, networkState: target.networkState, readyState: target.readyState, currentSrc: target.currentSrc, }); setIsLoading(false); setError("音频加载失败,请检查文件是否存在"); }; const handleLoadStart = () => { console.log("开始加载音频:", src); setIsLoading(true); setError(null); }; const handleCanPlay = () => { console.log("音频可以播放:", src); setIsLoading(false); setError(null); }; const handleCanPlayThrough = () => { console.log("音频可以流畅播放:", src); setIsLoading(false); setError(null); }; const handleLoad = () => { console.log("音频加载完成:", src); setIsLoading(false); setError(null); }; const handleAbort = () => { console.log("音频加载被中止:", src); }; const handleSuspend = () => { console.log("音频加载被暂停:", src); }; audio.addEventListener("loadedmetadata", handleLoadedMetadata); audio.addEventListener("timeupdate", handleTimeUpdate); audio.addEventListener("play", handlePlay); audio.addEventListener("pause", handlePause); audio.addEventListener("ended", handleEnded); audio.addEventListener("error", handleError); audio.addEventListener("loadstart", handleLoadStart); audio.addEventListener("canplay", handleCanPlay); audio.addEventListener("canplaythrough", handleCanPlayThrough); audio.addEventListener("load", handleLoad); audio.addEventListener("abort", handleAbort); audio.addEventListener("suspend", handleSuspend); return () => { audio.removeEventListener("loadedmetadata", handleLoadedMetadata); audio.removeEventListener("timeupdate", handleTimeUpdate); audio.removeEventListener("play", handlePlay); audio.removeEventListener("pause", handlePause); audio.removeEventListener("ended", handleEnded); audio.removeEventListener("error", handleError); audio.removeEventListener("loadstart", handleLoadStart); audio.removeEventListener("canplay", handleCanPlay); audio.removeEventListener("canplaythrough", handleCanPlayThrough); audio.removeEventListener("load", handleLoad); audio.removeEventListener("abort", handleAbort); audio.removeEventListener("suspend", handleSuspend); }; }, [dbDuration, src]); // 点击外部区域关闭菜单 useEffect(() => { const handleClickOutside = (event: MouseEvent) => { const target = event.target as Element; if (!target.closest(".audio-player-controls")) { setShowVolumeControl(false); setShowDownloadMenu(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, []); const togglePlay = () => { const audio = audioRef.current; if (!audio) return; if (isPlaying) { audio.pause(); } else { audio.play().catch((error) => { console.error("播放失败:", error); setError("播放失败,请重试"); }); } }; const handleProgressClick = (e: React.MouseEvent) => { const audio = audioRef.current; const progress = progressRef.current; if (!audio || !progress || duration === 0) return; const rect = progress.getBoundingClientRect(); const clickX = e.clientX - rect.left; const progressWidth = rect.width; const clickPercent = clickX / progressWidth; audio.currentTime = clickPercent * duration; }; const handleVolumeChange = (e: React.ChangeEvent) => { const audio = audioRef.current; if (!audio) return; const newVolume = parseFloat(e.target.value); setVolume(newVolume); audio.volume = newVolume; }; const handleDownload = () => { const link = document.createElement("a"); link.href = src; link.download = `${title || "recording"}.webm`; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; const formatTime = (time: number) => { if (isNaN(time) || time === Infinity) return "0:00"; const minutes = Math.floor(time / 60); const seconds = Math.floor(time % 60); return `${minutes}:${seconds.toString().padStart(2, "0")}`; }; return (
{/* 播放控制 */}
{title || "录音"}
{formatTime(currentTime)} / {formatTime(duration)}
{/* 控制按钮组 */}
{/* 音量控制 */}
{showVolumeControl && (
)}
{/* 下载按钮 */}
{showDownloadMenu && (
)}
{/* 进度条 */}
0 ? (currentTime / duration) * 100 : 0}%`, }} > {/* 进度条动画效果 */}
{/* 进度条悬停效果 */}
{/* 进度条滑块 */}
0 ? (currentTime / duration) * 100 : 0}%`, transform: "translate(-50%, -50%)", }} />
); }