Files
tongxunruanjian/pc-client/src/pages/chat/queryChat/MessageItem/SoundMessageRender.tsx
T

98 lines
2.7 KiB
TypeScript

import clsx from "clsx";
import { t } from "i18next";
import { FC, useEffect, useRef, useState } from "react";
import { feedbackToast } from "@/utils/common";
import { IMessageItemProps } from ".";
import styles from "./message-item.module.scss";
const PLAYED_KEY = "chaglian_played_sounds";
const getPlayedMap = (): Record<string, boolean> => {
try {
return JSON.parse(localStorage.getItem(PLAYED_KEY) ?? "{}");
} catch (error) {
return {};
}
};
const markPlayed = (clientMsgID: string) => {
const map = getPlayedMap();
map[clientMsgID] = true;
localStorage.setItem(PLAYED_KEY, JSON.stringify(map));
};
const SoundIcon = ({ className }: { className?: string }) => (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" className={className}>
<path
d="M4 9.5v5h3.5L12 18.5v-13L7.5 9.5H4Z"
fill="currentColor"
/>
<path
d="M15 9.2a4 4 0 0 1 0 5.6M17.5 6.8a7.4 7.4 0 0 1 0 10.4"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
/>
</svg>
);
const SoundMessageRender: FC<IMessageItemProps> = ({ message, isSender }) => {
const soundElem = message.soundElem;
const duration = Math.max(1, Math.round(soundElem?.duration ?? 0));
const [playing, setPlaying] = useState(false);
const [played, setPlayed] = useState(
() => Boolean(getPlayedMap()[message.clientMsgID]),
);
const audioRef = useRef<HTMLAudioElement>();
useEffect(() => {
return () => {
audioRef.current?.pause();
audioRef.current = undefined;
};
}, []);
// 宽度随时长微变:60~180px
const width = Math.min(180, 60 + duration * 6);
const play = () => {
const url = soundElem?.sourceUrl || soundElem?.soundPath;
if (!url || playing) return;
const audio = new Audio(url);
audioRef.current = audio;
setPlaying(true);
audio.onended = () => setPlaying(false);
audio.onerror = () => {
setPlaying(false);
feedbackToast({ msg: t("toast.accessFailed"), error: new Error("audio error") });
};
audio.play().catch(() => setPlaying(false));
markPlayed(message.clientMsgID);
setPlayed(true);
};
return (
<div className="flex items-center">
<div
className={clsx(
styles.bubble,
"flex cursor-pointer items-center justify-between text-sm",
isSender && "flex-row-reverse",
)}
style={{ width }}
onClick={play}
>
<SoundIcon className="shrink-0" />
<span className={clsx(isSender ? "mr-2" : "ml-2")}>{duration}</span>
</div>
{!isSender && !played && (
<span className="ml-2 h-2 w-2 shrink-0 rounded-full bg-[var(--warn-text)]" />
)}
</div>
);
};
export default SoundMessageRender;