mxivideo/src/components/AudioCard.tsx

185 lines
6.1 KiB
TypeScript

import React, { useState } from 'react'
import { Play, Pause, Music, Trash2, BarChart3, Clock, HardDrive, Hash } from 'lucide-react'
import { AudioFile, AudioService } from '../services/audioService'
interface AudioCardProps {
audio: AudioFile
onDelete: (audioId: string) => void
onShowChart: (audio: AudioFile) => void
}
const AudioCard: React.FC<AudioCardProps> = ({
audio,
onDelete,
onShowChart
}) => {
const [isPlaying, setIsPlaying] = useState(false)
const [audioElement, setAudioElement] = useState<HTMLAudioElement | null>(null)
const handlePlayPause = () => {
if (!audioElement) {
// 创建音频元素
const audio_el = new Audio(`file://${audio.file_path}`)
audio_el.addEventListener('ended', () => setIsPlaying(false))
audio_el.addEventListener('error', (e) => {
console.error('Audio playback error:', e)
setIsPlaying(false)
})
setAudioElement(audio_el)
audio_el.play()
.then(() => setIsPlaying(true))
.catch(err => {
console.error('Failed to play audio:', err)
setIsPlaying(false)
})
} else {
if (isPlaying) {
audioElement.pause()
setIsPlaying(false)
} else {
audioElement.play()
.then(() => setIsPlaying(true))
.catch(err => {
console.error('Failed to play audio:', err)
setIsPlaying(false)
})
}
}
}
const handleDelete = () => {
if (audioElement) {
audioElement.pause()
setAudioElement(null)
setIsPlaying(false)
}
onDelete(audio.id)
}
return (
<div className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden hover:shadow-md transition-shadow">
{/* 音频信息头部 */}
<div className="p-4 bg-gradient-to-r from-purple-500 to-pink-500 text-white">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="p-2 bg-white/20 rounded-lg">
<Music size={24} />
</div>
<div>
<h3 className="font-medium truncate max-w-48" title={audio.filename}>
{audio.filename}
</h3>
<p className="text-sm text-purple-100">
{audio.format.toUpperCase()} {AudioService.formatDuration(audio.duration)}
</p>
</div>
</div>
{/* 播放按钮 */}
<button
onClick={handlePlayPause}
className="p-2 bg-white/20 rounded-lg hover:bg-white/30 transition-colors"
title={isPlaying ? '暂停' : '播放'}
>
{isPlaying ? <Pause size={20} /> : <Play size={20} />}
</button>
</div>
</div>
{/* 音频详细信息 */}
<div className="p-4 space-y-3">
{/* 基本信息 */}
<div className="grid grid-cols-2 gap-4 text-sm">
<div className="flex items-center space-x-2">
<Clock size={14} className="text-gray-400" />
<span className="text-gray-600">:</span>
<span className="font-medium">{AudioService.formatDuration(audio.duration)}</span>
</div>
<div className="flex items-center space-x-2">
<HardDrive size={14} className="text-gray-400" />
<span className="text-gray-600">:</span>
<span className="font-medium">{AudioService.formatFileSize(audio.file_size)}</span>
</div>
</div>
{/* 音频参数 */}
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<span className="text-gray-600">:</span>
<span className="ml-2 font-medium">{audio.sample_rate} Hz</span>
</div>
<div>
<span className="text-gray-600">:</span>
<span className="ml-2 font-medium">{audio.channels}</span>
</div>
</div>
{/* 节奏信息 */}
{audio.tempo && (
<div className="text-sm">
<span className="text-gray-600">:</span>
<span className="ml-2 font-medium">{audio.tempo.toFixed(1)} BPM</span>
</div>
)}
{/* 频谱特征 */}
{audio.spectral_centroid && (
<div className="text-sm space-y-1">
<div>
<span className="text-gray-600">:</span>
<span className="ml-2 font-medium">{audio.spectral_centroid.toFixed(0)} Hz</span>
</div>
{audio.zero_crossing_rate && (
<div>
<span className="text-gray-600">:</span>
<span className="ml-2 font-medium">{(audio.zero_crossing_rate * 100).toFixed(2)}%</span>
</div>
)}
</div>
)}
{/* MD5哈希 */}
<div className="text-xs text-gray-500 flex items-center space-x-1">
<Hash size={12} />
<span>MD5:</span>
<code className="bg-gray-100 px-1 rounded font-mono">
{audio.md5_hash.substring(0, 8)}...
</code>
</div>
{/* 操作按钮 */}
<div className="flex items-center justify-between pt-2 border-t border-gray-100">
<div className="text-xs text-gray-400">
{new Date(audio.created_at).toLocaleDateString()}
</div>
<div className="flex space-x-2">
{/* 频率图按钮 */}
{audio.frequency_chart_path && (
<button
onClick={() => onShowChart(audio)}
className="p-2 text-gray-400 hover:text-blue-600 transition-colors"
title="查看频率图"
>
<BarChart3 size={16} />
</button>
)}
{/* 删除按钮 */}
<button
onClick={handleDelete}
className="p-2 text-gray-400 hover:text-red-600 transition-colors"
title="删除"
>
<Trash2 size={16} />
</button>
</div>
</div>
</div>
</div>
)
}
export default AudioCard