diff --git a/package.json b/package.json index a95aed3a..b8731c0f 100644 --- a/package.json +++ b/package.json @@ -65,8 +65,8 @@ "react-textarea-autosize": "^8.4.1", "react-use-wizard": "^2.2.1", "react-viewport-list": "^7.0.0", - "react-virtuoso": "^4.1.1", - "resolve": "^1.22.1", + "react-virtuoso": "^4.2.0", + "resolve": "^1.22.2", "rooks": "^7.10.0", "slate": "^0.93.0", "slate-history": "^0.93.0", @@ -77,7 +77,8 @@ "terser-webpack-plugin": "^5.3.7", "tippy.js": "^6.3.7", "typescript": "^5.0.3", - "webpack": "^5.77.0", + "wavesurfer.js": "^6.6.3", + "webpack": "^5.78.0", "webpack-dev-server": "^4.13.2", "webpack-manifest-plugin": "^5.0.0", "workbox-background-sync": "^6.5.4", @@ -125,6 +126,7 @@ "@types/masonry-layout": "^4.2.5", "@types/react-helmet": "^6.1.6", "@types/react-syntax-highlighter": "^15.5.6", + "@types/wavesurfer.js": "^6.0.4", "@typescript-eslint/eslint-plugin": "^5.57.1", "@typescript-eslint/parser": "^5.57.1", "@welldone-software/why-did-you-render": "^7.0.1", @@ -143,7 +145,7 @@ "md5-file": "^5.0.0", "patch-package": "^6.5.1", "postcss": "^8.4.21", - "postcss-loader": "^7.2.3", + "postcss-loader": "^7.2.4", "postinstall-postinstall": "^2.1.0", "prettier": "^2.8.7", "tailwindcss": "^3.3.1" diff --git a/public/locales/en/chat.json b/public/locales/en/chat.json index d3ffe957..cc996118 100644 --- a/public/locales/en/chat.json +++ b/public/locales/en/chat.json @@ -46,6 +46,7 @@ "file": "file", "image": "image", "forward": "forward", + "voice_message": "voice message", "voice": "Voice Chat", "deafen": "Deafen", "undeafen": "Undeafen", diff --git a/public/locales/zh/chat.json b/public/locales/zh/chat.json index ebf25a68..bd79898f 100644 --- a/public/locales/zh/chat.json +++ b/public/locales/zh/chat.json @@ -47,6 +47,7 @@ "file": "文件", "image": "图片", "forward": "转发", + "voice_message": "语音消息", "voice": "音频通话", "deafen": "静默", "undeafen": "取消静默", diff --git a/src/common/component/Message/Reply.tsx b/src/common/component/Message/Reply.tsx index 8024bfe5..dc720e3f 100644 --- a/src/common/component/Message/Reply.tsx +++ b/src/common/component/Message/Reply.tsx @@ -22,6 +22,11 @@ const renderContent = (data: MessagePayload) => { ); break; + case ContentTypes.audio: + res = ( + [Voice Message] + ); + break; case ContentTypes.markdown: res = (
diff --git a/src/common/component/Message/renderContent.tsx b/src/common/component/Message/renderContent.tsx index a3d288ae..f231e9b0 100644 --- a/src/common/component/Message/renderContent.tsx +++ b/src/common/component/Message/renderContent.tsx @@ -7,7 +7,7 @@ import MarkdownRender from "../MarkdownRender"; import FileMessage from "../FileMessage"; import { ContentType } from "../../../types/message"; import LinkifyText from '../LinkifyText'; -import VoiceMessage, { VoiceMessageProps } from "../VoiceMessage"; +import VoiceMessage from "../VoiceMessage"; type Props = { context: "user" | "channel"; @@ -55,7 +55,7 @@ const renderContent = ({ case ContentTypes.audio: { // const { url, secure_url } = properties; todo - ctn = ; + ctn = ; } break; case ContentTypes.file: diff --git a/src/common/component/Send/Replying.tsx b/src/common/component/Send/Replying.tsx index d6087167..bb9194a2 100644 --- a/src/common/component/Send/Replying.tsx +++ b/src/common/component/Send/Replying.tsx @@ -20,6 +20,13 @@ const renderContent = (data: MessagePayload) => { // }); break; + case ContentTypes.audio: + res = ( +
+ [Voice Message] +
+ ); + break; case ContentTypes.markdown: res = (
diff --git a/src/common/component/VoiceMessage.tsx b/src/common/component/VoiceMessage.tsx index d9bc4a66..63e3f6b7 100644 --- a/src/common/component/VoiceMessage.tsx +++ b/src/common/component/VoiceMessage.tsx @@ -1,4 +1,8 @@ -import { useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; +import WaveSurfer from 'wavesurfer.js'; +import IconPause from '../../assets/icons/pause.svg'; +import IconPlay from '../../assets/icons/play.circle.svg'; +import BASE_URL from '../../app/config'; export type VoiceMessageProps = { type: string, @@ -6,19 +10,70 @@ export type VoiceMessageProps = { secure_url: string, } -const VoiceMessage = ({ data }: { data: VoiceMessageProps }) => { - const [audio, setAudio] = useState(new Audio(data.url)); - // useEffect(() => { - // first - - // return () => { - // second - // } - // }, [url]) +const VoiceMessage = ({ file_path }: { file_path: string }) => { + const waveRef = useRef(null); + const containerRef = useRef(null); + const [playing, setPlaying] = useState(false); + const [duration, setDuration] = useState(0); + // const [audio, setAudio] = useState(new Audio(`${BASE_URL}/resource/file?file_path=${encodeURIComponent( + // file_path + // )}`)); + useEffect(() => { + let wave: WaveSurfer | null = null; + if (containerRef.current && file_path) { + const audioSrc = `${BASE_URL}/resource/file?file_path=${encodeURIComponent( + file_path + )}`; + wave = WaveSurfer.create({ + container: containerRef.current, + maxCanvasWidth: 200, + height: 32, + waveColor: '#0BA5EC', + cursorColor: '#0BA5AA', + progressColor: "#0BA5AA", + hideScrollbar: true, + // mediaControls: true, + normalize: true + }); + wave.load(audioSrc); + wave.on('play', function () { + setPlaying(true); + }); + wave.on('pause', function () { + setPlaying(false); + }); + wave.on('ready', function () { + if (waveRef.current) { + const dur = waveRef.current.getDuration(); + setDuration(Math.ceil(dur)); + } + }); + waveRef.current = wave; + } + return () => { + if (waveRef.current) { + waveRef.current.destroy(); + } + }; + }, [file_path]); + const handleClick = () => { + if (waveRef.current) { + if (waveRef.current.isPlaying()) { + waveRef.current.pause(); + } else { + waveRef.current.play(); + } + } + }; return ( -
- audio message +
+ +
+
+
); }; diff --git a/src/routes/chat/utils.tsx b/src/routes/chat/utils.tsx index 438bb5b8..7c092cbb 100644 --- a/src/routes/chat/utils.tsx +++ b/src/routes/chat/utils.tsx @@ -59,6 +59,11 @@ export const renderPreviewMessage = (message = null) => { res = ; } break; + case ContentTypes.audio: + { + res = `[${i18n.t("voice_message", { ns: "chat" })}]`; + } + break; case ContentTypes.markdown: { res = `[markdown]`; diff --git a/yarn.lock b/yarn.lock index 6b9266d9..92f94c7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2659,6 +2659,11 @@ dependencies: "@types/node" "*" +"@types/debounce@*": + version "1.2.1" + resolved "https://mirrors.cloud.tencent.com/npm/@types/debounce/-/debounce-1.2.1.tgz#79b65710bc8b6d44094d286aecf38e44f9627852" + integrity sha512-epMsEE85fi4lfmJUH/89/iV/LI+F5CvNIvmgs5g5jYFPfhO2S/ae8WSsLOKWdwtoaZw9Q2IhJ4tQ5tFCcS/4HA== + "@types/emoji-mart@^3.0.9": version "3.0.9" resolved "https://mirrors.cloud.tencent.com/npm/@types/emoji-mart/-/emoji-mart-3.0.9.tgz#2f7ef5d9ec194f28029c46c81a5fc1e5b0efa73c" @@ -2978,6 +2983,13 @@ resolved "https://mirrors.cloud.tencent.com/npm/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== +"@types/wavesurfer.js@^6.0.4": + version "6.0.4" + resolved "https://mirrors.cloud.tencent.com/npm/@types/wavesurfer.js/-/wavesurfer.js-6.0.4.tgz#7ae92d5752dba5b96df27560484da3cb4ff380f4" + integrity sha512-TGWy48OTZc9fuedxBmtM5WGNK4WDloRzf6/mE+OXmM9JuLzshhWZXSbZHiGnzpYB19mfdwnoEE4/8PNvyr/JBQ== + dependencies: + "@types/debounce" "*" + "@types/ws@^8.5.1": version "8.5.4" resolved "https://mirrors.cloud.tencent.com/npm/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" @@ -9658,10 +9670,10 @@ postcss-loader@^6.2.1: klona "^2.0.5" semver "^7.3.5" -postcss-loader@^7.2.3: - version "7.2.3" - resolved "https://mirrors.cloud.tencent.com/npm/postcss-loader/-/postcss-loader-7.2.3.tgz#c2d491a769102e0aac228d53fb1ad376cc158bb2" - integrity sha512-H/PIjgbn2q7zFnmUMPvEupIFnOAg+fYWC/4F6DugK8uPITVYyEflnSjLFV0u20B3Qi88PzPiAlzn8hBSu3f8oA== +postcss-loader@^7.2.4: + version "7.2.4" + resolved "https://mirrors.cloud.tencent.com/npm/postcss-loader/-/postcss-loader-7.2.4.tgz#2884f4ca172de633b2cf1f93dc852968f0632ba9" + integrity sha512-F88rpxxNspo5hatIc+orYwZDtHFaVFOSIVAx+fBfJC1GmhWbVmPWtmg2gXKE1OxJbneOSGn8PWdIwsZFcruS+w== dependencies: cosmiconfig "^8.1.3" cosmiconfig-typescript-loader "^4.3.0" @@ -10748,10 +10760,10 @@ react-viewport-list@^7.0.0: resolved "https://mirrors.cloud.tencent.com/npm/react-viewport-list/-/react-viewport-list-7.0.0.tgz#4d662dec3fcacd04dd99d0110c2145782ca6037d" integrity sha512-iI1R4yqqdAfDmov2BEDiFaHTCijfGYyquhdhMeTis4ke84pSW9Ccfbna6I9q+/7VAtV81atpIAIWQjU47dnh2Q== -react-virtuoso@^4.1.1: - version "4.1.1" - resolved "https://mirrors.cloud.tencent.com/npm/react-virtuoso/-/react-virtuoso-4.1.1.tgz#df42fe867377068562890752dc9282fde71499c7" - integrity sha512-G2lyifG5UIRZI9vgcBt+6OLDL6CO5/O6YiGS+O9z+VNRjVSTioSwfBC7sVSLspYq7iyL60aIXlVqSKVJiSVhlg== +react-virtuoso@^4.2.0: + version "4.2.0" + resolved "https://mirrors.cloud.tencent.com/npm/react-virtuoso/-/react-virtuoso-4.2.0.tgz#29d47c1075793ea5cbc6b9fc3f2345cf17749e2b" + integrity sha512-lO1akVyALlDMp+eIo4E99HjSQ8Cn2AKXBVfq7GaBjdlnlJaRvou8az6tVYGHFD6Az5EcPUc7OfzHvAyojOhgqw== react@^18.2.0: version "18.2.0" @@ -10979,6 +10991,15 @@ resolve@^1.1.7, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22 path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +resolve@^1.22.2: + version "1.22.2" + resolved "https://mirrors.cloud.tencent.com/npm/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" + integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== + dependencies: + is-core-module "^2.11.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^2.0.0-next.4: version "2.0.0-next.4" resolved "https://mirrors.cloud.tencent.com/npm/resolve/-/resolve-2.0.0-next.4.tgz#3d37a113d6429f496ec4752d2a2e58efb1fd4660" @@ -12492,6 +12513,11 @@ watchpack@^2.4.0: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" +wavesurfer.js@^6.6.3: + version "6.6.3" + resolved "https://mirrors.cloud.tencent.com/npm/wavesurfer.js/-/wavesurfer.js-6.6.3.tgz#eadebf0542be702f1eef2f52c3e312b839030166" + integrity sha512-XqUOXe8+SOTe8uKCHaqW0vJ5etCCQvq/NgaPycn9HAX/nUi+2zoWD+w9i7H5vBT9UCDNawOia+vS5Ct3kZGQzA== + wbuf@^1.1.0, wbuf@^1.7.3: version "1.7.3" resolved "https://mirrors.cloud.tencent.com/npm/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" @@ -12669,10 +12695,10 @@ webpack@^5.64.4: watchpack "^2.4.0" webpack-sources "^3.2.3" -webpack@^5.77.0: - version "5.77.0" - resolved "https://mirrors.cloud.tencent.com/npm/webpack/-/webpack-5.77.0.tgz#dea3ad16d7ea6b84aa55fa42f4eac9f30e7eb9b4" - integrity sha512-sbGNjBr5Ya5ss91yzjeJTLKyfiwo5C628AFjEa6WSXcZa4E+F57om3Cc8xLb1Jh0b243AWuSYRf3dn7HVeFQ9Q== +webpack@^5.78.0: + version "5.78.0" + resolved "https://mirrors.cloud.tencent.com/npm/webpack/-/webpack-5.78.0.tgz#836452a12416af2a7beae906b31644cb2562f9e6" + integrity sha512-gT5DP72KInmE/3azEaQrISjTvLYlSM0j1Ezhht/KLVkrqtv10JoP/RXhwmX/frrutOPuSq3o5Vq0ehR/4Vmd1g== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^0.0.51"