Добавлены новые зависимости для работы с эмодзи и улучшена структура базы данных. Реализована модель сообщений и обновлены компоненты для поддержки новых функций мессенджера. Обновлены запросы и мутации для работы с сообщениями и чатом.
This commit is contained in:
174
src/components/ui/voice-player.tsx
Normal file
174
src/components/ui/voice-player.tsx
Normal file
@ -0,0 +1,174 @@
|
||||
"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])
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user