Files
sfera/src/components/ui/voice-player.tsx

174 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState, useRef, useEffect } from 'react'
import { Button } from '@/components/ui/button'
import { Play, Pause, Volume2 } from 'lucide-react'
interface VoicePlayerProps {
audioUrl: string
duration?: number
isCurrentUser?: boolean
}
export function VoicePlayer({ audioUrl, duration = 0, isCurrentUser = false }: VoicePlayerProps) {
const [isPlaying, setIsPlaying] = useState(false)
const [currentTime, setCurrentTime] = useState(0)
const [audioDuration, setAudioDuration] = useState(duration > 0 ? duration : 0)
const [isLoading, setIsLoading] = useState(false)
const audioRef = useRef<HTMLAudioElement | null>(null)
// Обновляем длительность при изменении props
useEffect(() => {
if (duration > 0 && (!audioDuration || audioDuration === 0)) {
setAudioDuration(duration)
}
}, [duration, audioDuration])
useEffect(() => {
// Создаем аудио элемент
audioRef.current = new Audio(audioUrl)
const audio = audioRef.current
const handleLoadedMetadata = () => {
if (audio.duration && isFinite(audio.duration) && !isNaN(audio.duration)) {
setAudioDuration(audio.duration)
} else {
setAudioDuration(duration || 0)
}
setIsLoading(false)
}
const handleTimeUpdate = () => {
if (audio.currentTime && isFinite(audio.currentTime) && !isNaN(audio.currentTime)) {
setCurrentTime(audio.currentTime)
}
}
const handleEnded = () => {
setIsPlaying(false)
setCurrentTime(0)
}
const handleCanPlay = () => {
setIsLoading(false)
}
const handleLoadStart = () => {
setIsLoading(true)
}
const handleError = () => {
console.error('Audio loading error')
setIsLoading(false)
setIsPlaying(false)
}
audio.addEventListener('loadedmetadata', handleLoadedMetadata)
audio.addEventListener('timeupdate', handleTimeUpdate)
audio.addEventListener('ended', handleEnded)
audio.addEventListener('canplay', handleCanPlay)
audio.addEventListener('loadstart', handleLoadStart)
audio.addEventListener('error', handleError)
return () => {
if (audio) {
audio.removeEventListener('loadedmetadata', handleLoadedMetadata)
audio.removeEventListener('timeupdate', handleTimeUpdate)
audio.removeEventListener('ended', handleEnded)
audio.removeEventListener('canplay', handleCanPlay)
audio.removeEventListener('loadstart', handleLoadStart)
audio.removeEventListener('error', handleError)
audio.pause()
}
}
}, [audioUrl, duration])
const togglePlayPause = () => {
const audio = audioRef.current
if (!audio) return
if (isPlaying) {
audio.pause()
setIsPlaying(false)
} else {
audio.play().then(() => {
setIsPlaying(true)
}).catch((error) => {
console.error('Error playing audio:', error)
setIsPlaying(false)
})
}
}
const formatTime = (time: number) => {
if (isNaN(time) || !isFinite(time) || time < 0) return '0:00'
const minutes = Math.floor(time / 60)
const seconds = Math.floor(time % 60)
return `${minutes}:${seconds.toString().padStart(2, '0')}`
}
const getProgress = () => {
if (!audioDuration || audioDuration === 0 || isNaN(audioDuration) || !isFinite(audioDuration)) return 0
if (!currentTime || isNaN(currentTime) || !isFinite(currentTime)) return 0
return Math.min((currentTime / audioDuration) * 100, 100)
}
return (
<div className={`flex items-center space-x-4 p-3 rounded-lg min-w-[200px] max-w-sm ${
isCurrentUser
? 'bg-blue-500/20 border border-blue-500/30'
: 'bg-white/10 border border-white/20'
}`}>
{/* Кнопка воспроизведения */}
<Button
onClick={togglePlayPause}
disabled={isLoading}
variant="ghost"
size="sm"
className={`p-2 rounded-full ${
isCurrentUser
? 'text-blue-300 hover:text-blue-200 hover:bg-blue-500/30'
: 'text-white hover:text-white/80 hover:bg-white/20'
}`}
>
{isLoading ? (
<div className="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" />
) : isPlaying ? (
<Pause className="h-4 w-4" />
) : (
<Play className="h-4 w-4" />
)}
</Button>
{/* Визуализация волны / прогресс бар */}
<div className="flex-1 space-y-2">
<div className="flex items-center space-x-3">
<Volume2 className={`h-3 w-3 ${
isCurrentUser ? 'text-blue-300' : 'text-white/60'
}`} />
<div className="flex-1 h-1 bg-white/20 rounded-full overflow-hidden">
<div
className={`h-full transition-all duration-300 ${
isCurrentUser ? 'bg-blue-400' : 'bg-white/60'
}`}
style={{ width: `${getProgress()}%` }}
/>
</div>
</div>
{/* Время */}
<div className="flex justify-between items-center text-xs">
<span className={`${isCurrentUser ? 'text-blue-300/70' : 'text-white/50'} min-w-[2rem]`}>
{formatTime(currentTime)}
</span>
<span className={`${isCurrentUser ? 'text-blue-300/70' : 'text-white/50'} min-w-[2rem] text-right`}>
{formatTime(audioDuration)}
</span>
</div>
</div>
</div>
)
}