From f5fc85e1a7a302a68ceb9dcc996d11c58aba284f Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Wed, 14 Jun 2023 16:22:48 +0100 Subject: [PATCH] Created a waveform component to display the recording's waveform --- .pnp.cjs | 32 +++++++++++++++++++++ package.json | 2 ++ src/components/Waveform.tsx | 56 +++++++++++++++++++++++++++++++++++++ src/pages/test.tsx | 23 +++++++++++---- yarn.lock | 25 +++++++++++++++++ 5 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 src/components/Waveform.tsx diff --git a/.pnp.cjs b/.pnp.cjs index 450ba249..9c0ee25e 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -37,6 +37,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@types/react", "npm:18.0.27"],\ ["@types/react-dom", "npm:18.0.10"],\ ["@types/uuid", "npm:9.0.1"],\ + ["@types/wavesurfer.js", "npm:6.0.6"],\ ["@wixc3/react-board", "virtual:bf6aaa3c042cc1a42ee2910afd2615e1727d392da84fae76c4ff4b04654d9bc15e6952fab427843d598f4a94d8eaa3198f30dab7b7c8d523ed6cd30ef92cfb66#npm:2.2.0"],\ ["autoprefixer", "virtual:bf6aaa3c042cc1a42ee2910afd2615e1727d392da84fae76c4ff4b04654d9bc15e6952fab427843d598f4a94d8eaa3198f30dab7b7c8d523ed6cd30ef92cfb66#npm:10.4.14"],\ ["axios", "npm:1.3.5"],\ @@ -68,6 +69,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["tailwindcss", "virtual:bf6aaa3c042cc1a42ee2910afd2615e1727d392da84fae76c4ff4b04654d9bc15e6952fab427843d598f4a94d8eaa3198f30dab7b7c8d523ed6cd30ef92cfb66#npm:3.3.1"],\ ["typescript", "patch:typescript@npm%3A4.9.5#~builtin::version=4.9.5&hash=289587"],\ ["uuid", "npm:9.0.0"],\ + ["wavesurfer.js", "npm:6.6.4"],\ ["zustand", "virtual:bf6aaa3c042cc1a42ee2910afd2615e1727d392da84fae76c4ff4b04654d9bc15e6952fab427843d598f4a94d8eaa3198f30dab7b7c8d523ed6cd30ef92cfb66#npm:4.3.7"]\ ],\ "linkType": "SOFT"\ @@ -1770,6 +1772,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["@types/debounce", [\ + ["npm:3.0.0", {\ + "packageLocation": "./.yarn/cache/@types-debounce-npm-3.0.0-b7858a5be9-af99f44f8c.zip/node_modules/@types/debounce/",\ + "packageDependencies": [\ + ["@types/debounce", "npm:3.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@types/express", [\ ["npm:4.17.17", {\ "packageLocation": "./.yarn/cache/@types-express-npm-4.17.17-46fe8173db-0196dacc27.zip/node_modules/@types/express/",\ @@ -1989,6 +2000,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["@types/wavesurfer.js", [\ + ["npm:6.0.6", {\ + "packageLocation": "./.yarn/cache/@types-wavesurfer.js-npm-6.0.6-465818fff7-732e998507.zip/node_modules/@types/wavesurfer.js/",\ + "packageDependencies": [\ + ["@types/wavesurfer.js", "npm:6.0.6"],\ + ["@types/debounce", "npm:3.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@typescript-eslint/parser", [\ ["npm:5.51.0", {\ "packageLocation": "./.yarn/cache/@typescript-eslint-parser-npm-5.51.0-884b6bb8aa-096ec81913.zip/node_modules/@typescript-eslint/parser/",\ @@ -5343,6 +5364,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@types/react", "npm:18.0.27"],\ ["@types/react-dom", "npm:18.0.10"],\ ["@types/uuid", "npm:9.0.1"],\ + ["@types/wavesurfer.js", "npm:6.0.6"],\ ["@wixc3/react-board", "virtual:bf6aaa3c042cc1a42ee2910afd2615e1727d392da84fae76c4ff4b04654d9bc15e6952fab427843d598f4a94d8eaa3198f30dab7b7c8d523ed6cd30ef92cfb66#npm:2.2.0"],\ ["autoprefixer", "virtual:bf6aaa3c042cc1a42ee2910afd2615e1727d392da84fae76c4ff4b04654d9bc15e6952fab427843d598f4a94d8eaa3198f30dab7b7c8d523ed6cd30ef92cfb66#npm:10.4.14"],\ ["axios", "npm:1.3.5"],\ @@ -5374,6 +5396,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["tailwindcss", "virtual:bf6aaa3c042cc1a42ee2910afd2615e1727d392da84fae76c4ff4b04654d9bc15e6952fab427843d598f4a94d8eaa3198f30dab7b7c8d523ed6cd30ef92cfb66#npm:3.3.1"],\ ["typescript", "patch:typescript@npm%3A4.9.5#~builtin::version=4.9.5&hash=289587"],\ ["uuid", "npm:9.0.0"],\ + ["wavesurfer.js", "npm:6.6.4"],\ ["zustand", "virtual:bf6aaa3c042cc1a42ee2910afd2615e1727d392da84fae76c4ff4b04654d9bc15e6952fab427843d598f4a94d8eaa3198f30dab7b7c8d523ed6cd30ef92cfb66#npm:4.3.7"]\ ],\ "linkType": "SOFT"\ @@ -7209,6 +7232,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["wavesurfer.js", [\ + ["npm:6.6.4", {\ + "packageLocation": "./.yarn/cache/wavesurfer.js-npm-6.6.4-ee0823a013-cee2a98695.zip/node_modules/wavesurfer.js/",\ + "packageDependencies": [\ + ["wavesurfer.js", "npm:6.6.4"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["webcrypto-core", [\ ["npm:1.7.7", {\ "packageLocation": "./.yarn/cache/webcrypto-core-npm-1.7.7-bb22025843-1dc5aedb25.zip/node_modules/webcrypto-core/",\ diff --git a/package.json b/package.json index aaf38463..a710638e 100644 --- a/package.json +++ b/package.json @@ -43,11 +43,13 @@ "swr": "^2.1.3", "typescript": "4.9.5", "uuid": "^9.0.0", + "wavesurfer.js": "^6.6.4", "zustand": "^4.3.6" }, "devDependencies": { "@types/lodash": "^4.14.191", "@types/uuid": "^9.0.1", + "@types/wavesurfer.js": "^6.0.6", "@wixc3/react-board": "^2.2.0", "autoprefixer": "^10.4.13", "postcss": "^8.4.21", diff --git a/src/components/Waveform.tsx b/src/components/Waveform.tsx new file mode 100644 index 00000000..34df9d5c --- /dev/null +++ b/src/components/Waveform.tsx @@ -0,0 +1,56 @@ +import React, {useEffect, useRef} from "react"; +import WaveSurfer from "wavesurfer.js"; + +interface Props { + audio: string; + isPlaying: boolean; + onEnd: () => void; +} + +const Waveform = ({audio, isPlaying, onEnd}: Props) => { + const containerRef = useRef(null); + const waveSurferRef = useRef(null); + + useEffect(() => { + const waveSurfer = WaveSurfer.create({ + container: containerRef?.current || "", + responsive: true, + cursorWidth: 0, + height: 24, + waveColor: "#FCDDEC", + progressColor: "#EF5DA8", + barGap: 4, + barWidth: 4, + barRadius: 2, + fillParent: true, + hideScrollbar: true, + normalize: true, + }); + waveSurfer.load(audio); + waveSurfer.on("ready", () => { + waveSurferRef.current = waveSurfer; + }); + waveSurfer.on("finish", onEnd); + + return () => { + waveSurfer.destroy(); + }; + }, [audio, onEnd]); + + useEffect(() => { + if (isPlaying) waveSurferRef.current?.play(); + if (!isPlaying) waveSurferRef.current?.pause(); + }, [isPlaying]); + + return ( + <> + +
+ + ); +}; + +export default Waveform; diff --git a/src/pages/test.tsx b/src/pages/test.tsx index 163aa853..ea6a2f98 100644 --- a/src/pages/test.tsx +++ b/src/pages/test.tsx @@ -8,9 +8,10 @@ import {sessionOptions} from "@/lib/session"; import useUser from "@/hooks/useUser"; import Sidebar from "@/components/Sidebar"; import dynamic from "next/dynamic"; -import {BsCheckCircleFill, BsMicFill, BsPauseCircle, BsPlayCircle, BsPlayFill, BsTrashFill} from "react-icons/bs"; +import {BsCheckCircleFill, BsMicFill, BsPauseCircle, BsPauseFill, BsPlayCircle, BsPlayFill, BsTrashFill} from "react-icons/bs"; import {useEffect, useState} from "react"; +const Waveform = dynamic(() => import("../components/Waveform"), {ssr: false}); const ReactMediaRecorder = dynamic(() => import("react-media-recorder").then((mod) => mod.ReactMediaRecorder), { ssr: false, }); @@ -38,6 +39,7 @@ export default function Page() { const {user} = useUser({redirectTo: "/login"}); const [recordingDuration, setRecordingDuration] = useState(0); const [isRecording, setIsRecording] = useState(false); + const [isPlaying, setIsPlaying] = useState(false); useEffect(() => { let recordingInterval: NodeJS.Timer | undefined = undefined; @@ -78,7 +80,7 @@ export default function Page() {
{status === "idle" && ( <> - {!mediaBlobUrl &&
} +
{status === "idle" && ( { @@ -155,10 +157,21 @@ export default function Page() {
)} - {status === "stopped" && ( + {status === "stopped" && mediaBlobUrl && ( <> - -
+ {isPlaying && ( + setIsPlaying(false)} + /> + )} + {!isPlaying && ( + setIsPlaying(true)} + /> + )} + setIsPlaying(false)} />