Ajusta player de audio e permite seek
All checks were successful
All checks were successful
This commit is contained in:
parent
d125930cf6
commit
bc67dc01ef
4 changed files with 80 additions and 33 deletions
|
|
@ -133,7 +133,7 @@ function AudioWaveform({
|
||||||
}) {
|
}) {
|
||||||
const playedBars = Math.round(progress * peaks.length)
|
const playedBars = Math.round(progress * peaks.length)
|
||||||
return (
|
return (
|
||||||
<div className="flex h-8 items-center gap-[2px]">
|
<div className="flex h-8 items-center gap-[2px] overflow-hidden">
|
||||||
{peaks.map((value, index) => {
|
{peaks.map((value, index) => {
|
||||||
const height = Math.max(4, Math.round(value * 24))
|
const height = Math.max(4, Math.round(value * 24))
|
||||||
const played = index <= playedBars
|
const played = index <= playedBars
|
||||||
|
|
@ -191,7 +191,7 @@ function AudioAttachmentPlayer({
|
||||||
const decoded = await audioContext.decodeAudioData(buffer)
|
const decoded = await audioContext.decodeAudioData(buffer)
|
||||||
await audioContext.close()
|
await audioContext.close()
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setPeaks(extractPeaks(decoded))
|
setPeaks(extractPeaks(decoded, 36))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Falha ao gerar waveform:", error)
|
console.error("Falha ao gerar waveform:", error)
|
||||||
|
|
@ -246,10 +246,11 @@ function AudioAttachmentPlayer({
|
||||||
|
|
||||||
const progress = duration > 0 ? currentTime / duration : 0
|
const progress = duration > 0 ? currentTime / duration : 0
|
||||||
const sizeLabel = formatAttachmentSize(size)
|
const sizeLabel = formatAttachmentSize(size)
|
||||||
|
const canSeek = duration > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex items-center gap-3 rounded-2xl border px-3 py-2 ${
|
className={`flex w-full max-w-[280px] items-center gap-3 rounded-2xl border px-3 py-2 ${
|
||||||
isAgent ? "border-white/10 bg-white/10 text-white" : "border-slate-200 bg-white text-slate-900"
|
isAgent ? "border-white/10 bg-white/10 text-white" : "border-slate-200 bg-white text-slate-900"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|
@ -264,14 +265,34 @@ function AudioAttachmentPlayer({
|
||||||
{isPlaying ? <Pause className="size-4" /> : <Play className="size-4" />}
|
{isPlaying ? <Pause className="size-4" /> : <Play className="size-4" />}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="flex-1">
|
<div className="flex-1 min-w-0">
|
||||||
{isLoadingWaveform ? (
|
<button
|
||||||
<div className={`h-8 rounded-lg ${isAgent ? "bg-white/10" : "bg-slate-100"}`} />
|
type="button"
|
||||||
) : peaks.length > 0 ? (
|
onPointerDown={(event) => {
|
||||||
<AudioWaveform peaks={peaks} progress={progress} isAgent={isAgent} />
|
if (!audioRef.current || duration <= 0) return
|
||||||
) : (
|
const rect = event.currentTarget.getBoundingClientRect()
|
||||||
<div className={`h-8 rounded-lg ${isAgent ? "bg-white/10" : "bg-slate-100"}`} />
|
if (rect.width <= 0) return
|
||||||
)}
|
const ratio = (event.clientX - rect.left) / rect.width
|
||||||
|
const clamped = Math.min(1, Math.max(0, ratio))
|
||||||
|
const targetTime = clamped * duration
|
||||||
|
audioRef.current.currentTime = targetTime
|
||||||
|
setCurrentTime(targetTime)
|
||||||
|
}}
|
||||||
|
aria-label="Buscar posição do áudio"
|
||||||
|
className={[
|
||||||
|
"w-full rounded-lg bg-transparent p-0 text-left transition-colors focus-visible:outline-none",
|
||||||
|
canSeek ? "cursor-pointer" : "cursor-default",
|
||||||
|
canSeek ? (isAgent ? "hover:bg-white/10" : "hover:bg-slate-100") : "",
|
||||||
|
].join(" ")}
|
||||||
|
>
|
||||||
|
{isLoadingWaveform ? (
|
||||||
|
<div className={`h-8 rounded-lg ${isAgent ? "bg-white/10" : "bg-slate-100"}`} />
|
||||||
|
) : peaks.length > 0 ? (
|
||||||
|
<AudioWaveform peaks={peaks} progress={progress} isAgent={isAgent} />
|
||||||
|
) : (
|
||||||
|
<div className={`h-8 rounded-lg ${isAgent ? "bg-white/10" : "bg-slate-100"}`} />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
<div className={`mt-1 flex items-center justify-between text-[10px] ${isAgent ? "text-white/60" : "text-slate-400"}`}>
|
<div className={`mt-1 flex items-center justify-between text-[10px] ${isAgent ? "text-white/60" : "text-slate-400"}`}>
|
||||||
<span>{formatDuration(currentTime)}</span>
|
<span>{formatDuration(currentTime)}</span>
|
||||||
<span className="truncate">{sizeLabel ?? formatDuration(duration)}</span>
|
<span className="truncate">{sizeLabel ?? formatDuration(duration)}</span>
|
||||||
|
|
@ -282,7 +303,9 @@ function AudioAttachmentPlayer({
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
className={`flex size-8 items-center justify-center rounded-md ${
|
className={`flex size-8 items-center justify-center rounded-md ${
|
||||||
isAgent ? "text-white/70 hover:text-white" : "text-slate-500 hover:text-slate-700"
|
isAgent
|
||||||
|
? "text-white/70 hover:bg-white/10 hover:text-white"
|
||||||
|
: "text-slate-500 hover:bg-slate-100 hover:text-slate-700"
|
||||||
}`}
|
}`}
|
||||||
aria-label="Baixar áudio"
|
aria-label="Baixar áudio"
|
||||||
>
|
>
|
||||||
|
|
@ -1147,12 +1170,12 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={att.storageId}
|
key={att.storageId}
|
||||||
className="flex items-center gap-2 rounded-lg bg-slate-100 px-2 py-1 text-xs"
|
className="flex w-full items-center gap-2 rounded-lg bg-slate-100 px-2 py-1 text-xs"
|
||||||
>
|
>
|
||||||
<audio controls src={att.previewUrl} className="h-7 w-40" />
|
<audio controls src={att.previewUrl} className="h-8 w-full min-w-0" />
|
||||||
<button
|
<button
|
||||||
onClick={() => handleRemoveAttachment(att.storageId)}
|
onClick={() => handleRemoveAttachment(att.storageId)}
|
||||||
className="flex size-6 items-center justify-center rounded-full text-slate-500 hover:bg-slate-200"
|
className="flex size-6 shrink-0 items-center justify-center rounded-full text-slate-500 hover:bg-slate-200"
|
||||||
aria-label="Remover áudio"
|
aria-label="Remover áudio"
|
||||||
>
|
>
|
||||||
<X className="size-3" />
|
<X className="size-3" />
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ function AudioWaveform({
|
||||||
}) {
|
}) {
|
||||||
const playedBars = Math.round(progress * peaks.length)
|
const playedBars = Math.round(progress * peaks.length)
|
||||||
return (
|
return (
|
||||||
<div className="flex h-9 items-center gap-[2px]">
|
<div className="flex h-9 items-center gap-[2px] overflow-hidden">
|
||||||
{peaks.map((value, index) => {
|
{peaks.map((value, index) => {
|
||||||
const height = Math.max(4, Math.round(value * 28))
|
const height = Math.max(4, Math.round(value * 28))
|
||||||
const played = index <= playedBars
|
const played = index <= playedBars
|
||||||
|
|
@ -100,7 +100,7 @@ function AudioAttachmentPlayer({
|
||||||
const decoded = await audioContext.decodeAudioData(buffer)
|
const decoded = await audioContext.decodeAudioData(buffer)
|
||||||
await audioContext.close()
|
await audioContext.close()
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setPeaks(extractPeaks(decoded))
|
setPeaks(extractPeaks(decoded, 36))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Falha ao gerar waveform:", error)
|
console.error("Falha ao gerar waveform:", error)
|
||||||
|
|
@ -155,11 +155,22 @@ function AudioAttachmentPlayer({
|
||||||
|
|
||||||
const progress = duration > 0 ? currentTime / duration : 0
|
const progress = duration > 0 ? currentTime / duration : 0
|
||||||
const sizeLabel = formatAttachmentSize(size)
|
const sizeLabel = formatAttachmentSize(size)
|
||||||
|
const canSeek = duration > 0
|
||||||
|
const handleSeek = (event: { currentTarget: HTMLButtonElement; clientX: number }) => {
|
||||||
|
if (!audioRef.current || duration <= 0) return
|
||||||
|
const rect = event.currentTarget.getBoundingClientRect()
|
||||||
|
if (rect.width <= 0) return
|
||||||
|
const ratio = (event.clientX - rect.left) / rect.width
|
||||||
|
const clamped = Math.min(1, Math.max(0, ratio))
|
||||||
|
const targetTime = clamped * duration
|
||||||
|
audioRef.current.currentTime = targetTime
|
||||||
|
setCurrentTime(targetTime)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-3 rounded-2xl border px-3 py-2",
|
"flex w-full max-w-[280px] items-center gap-3 rounded-2xl border px-3 py-2",
|
||||||
tone === "dark"
|
tone === "dark"
|
||||||
? "border-white/10 bg-white/10 text-white"
|
? "border-white/10 bg-white/10 text-white"
|
||||||
: "border-slate-200 bg-white text-slate-900"
|
: "border-slate-200 bg-white text-slate-900"
|
||||||
|
|
@ -179,14 +190,25 @@ function AudioAttachmentPlayer({
|
||||||
{isPlaying ? <Pause className="size-4" /> : <Play className="size-4" />}
|
{isPlaying ? <Pause className="size-4" /> : <Play className="size-4" />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="flex-1">
|
<div className="flex-1 min-w-0">
|
||||||
{isLoadingWaveform ? (
|
<button
|
||||||
<div className={cn("h-9 rounded-lg", tone === "dark" ? "bg-white/10" : "bg-slate-100")} />
|
type="button"
|
||||||
) : peaks.length > 0 ? (
|
onPointerDown={handleSeek}
|
||||||
<AudioWaveform peaks={peaks} progress={progress} tone={tone} />
|
aria-label="Buscar posição do áudio"
|
||||||
) : (
|
className={cn(
|
||||||
<div className={cn("h-9 rounded-lg", tone === "dark" ? "bg-white/10" : "bg-slate-100")} />
|
"w-full rounded-lg bg-transparent p-0 text-left transition-colors focus-visible:outline-none",
|
||||||
)}
|
canSeek ? "cursor-pointer" : "cursor-default",
|
||||||
|
canSeek && (tone === "dark" ? "hover:bg-white/10" : "hover:bg-slate-100")
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isLoadingWaveform ? (
|
||||||
|
<div className={cn("h-9 rounded-lg", tone === "dark" ? "bg-white/10" : "bg-slate-100")} />
|
||||||
|
) : peaks.length > 0 ? (
|
||||||
|
<AudioWaveform peaks={peaks} progress={progress} tone={tone} />
|
||||||
|
) : (
|
||||||
|
<div className={cn("h-9 rounded-lg", tone === "dark" ? "bg-white/10" : "bg-slate-100")} />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-1 flex items-center justify-between text-[10px]",
|
"mt-1 flex items-center justify-between text-[10px]",
|
||||||
|
|
@ -204,7 +226,9 @@ function AudioAttachmentPlayer({
|
||||||
size="icon"
|
size="icon"
|
||||||
className={cn(
|
className={cn(
|
||||||
"size-8",
|
"size-8",
|
||||||
tone === "dark" ? "text-white/70 hover:text-white" : "text-slate-500 hover:text-slate-700"
|
tone === "dark"
|
||||||
|
? "text-white/70 hover:bg-white/10 hover:text-white"
|
||||||
|
: "text-slate-500 hover:bg-slate-100 hover:text-slate-700"
|
||||||
)}
|
)}
|
||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
aria-label="Baixar áudio"
|
aria-label="Baixar áudio"
|
||||||
|
|
|
||||||
|
|
@ -826,12 +826,12 @@ export function ChatWidget() {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="flex items-center gap-2 rounded-lg border border-slate-200 bg-white px-2 py-1"
|
className="flex w-full items-center gap-2 rounded-lg border border-slate-200 bg-white px-2 py-1"
|
||||||
>
|
>
|
||||||
<audio controls src={file.previewUrl} className="h-8 w-40" />
|
<audio controls src={file.previewUrl} className="h-9 w-full min-w-0" />
|
||||||
<button
|
<button
|
||||||
onClick={() => removeAttachment(index)}
|
onClick={() => removeAttachment(index)}
|
||||||
className="flex size-6 items-center justify-center rounded-full text-slate-500 hover:bg-slate-100"
|
className="flex size-6 shrink-0 items-center justify-center rounded-full text-slate-500 hover:bg-slate-100"
|
||||||
aria-label="Remover áudio"
|
aria-label="Remover áudio"
|
||||||
>
|
>
|
||||||
<X className="size-3" />
|
<X className="size-3" />
|
||||||
|
|
|
||||||
|
|
@ -547,8 +547,8 @@ export function TicketChatPanel({ ticketId }: TicketChatPanelProps) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{pendingAudio && (
|
{pendingAudio && (
|
||||||
<div className="mb-2 flex items-center gap-2 rounded-lg border border-slate-200 bg-white px-2 py-1">
|
<div className="mb-2 flex w-full items-center gap-2 rounded-lg border border-slate-200 bg-white px-2 py-1">
|
||||||
<audio controls src={pendingAudio.previewUrl} className="h-8 w-44" />
|
<audio controls src={pendingAudio.previewUrl} className="h-9 w-full min-w-0" />
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -557,7 +557,7 @@ export function TicketChatPanel({ ticketId }: TicketChatPanelProps) {
|
||||||
}
|
}
|
||||||
setPendingAudio(null)
|
setPendingAudio(null)
|
||||||
}}
|
}}
|
||||||
className="flex size-7 items-center justify-center rounded-full text-slate-500 hover:bg-slate-100"
|
className="flex size-7 shrink-0 items-center justify-center rounded-full text-slate-500 hover:bg-slate-100"
|
||||||
aria-label="Remover áudio"
|
aria-label="Remover áudio"
|
||||||
>
|
>
|
||||||
<X className="size-3.5" />
|
<X className="size-3.5" />
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue