import React, { useRef, useEffect, useState } from "react"; import WaveSurfer from "wavesurfer.js"; import RegionsPlugin from "wavesurfer.js/dist/plugins/regions.esm.js"; import { formatTime, formatTimeMs } from "../../lib/time"; import { Topic } from "./webSocketTypes"; import { AudioWaveform } from "../../api"; import { waveSurferStyles } from "../../styles/recorder"; import { Box, Flex, IconButton } from "@chakra-ui/react"; import { LuPause, LuPlay } from "react-icons/lu"; type PlayerProps = { topics: Topic[]; useActiveTopic: [ Topic | null, React.Dispatch>, ]; waveform: AudioWaveform; media: HTMLMediaElement; mediaDuration: number; }; export default function Player(props: PlayerProps) { const waveformRef = useRef(null); const [wavesurfer, setWavesurfer] = useState(null); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [waveRegions, setWaveRegions] = useState(null); const [activeTopic, setActiveTopic] = props.useActiveTopic; const topicsRef = useRef(props.topics); const [firstRender, setFirstRender] = useState(true); const keyHandler = (e) => { if (e.key == " ") { wavesurfer?.playPause(); } }; useEffect(() => { document.addEventListener("keyup", keyHandler); return () => { document.removeEventListener("keyup", keyHandler); }; }); // Waveform setup useEffect(() => { if (waveformRef.current) { const _wavesurfer = WaveSurfer.create({ container: waveformRef.current, peaks: [props.waveform.data], height: "auto", duration: Math.floor(props.mediaDuration / 1000), media: props.media, ...waveSurferStyles.playerSettings, }); // styling const wsWrapper = _wavesurfer.getWrapper(); wsWrapper.style.cursor = waveSurferStyles.playerStyle.cursor; wsWrapper.style.backgroundColor = waveSurferStyles.playerStyle.backgroundColor; wsWrapper.style.borderRadius = waveSurferStyles.playerStyle.borderRadius; _wavesurfer.on("play", () => { setIsPlaying(true); }); _wavesurfer.on("pause", () => { setIsPlaying(false); }); _wavesurfer.on("timeupdate", setCurrentTime); setWaveRegions(_wavesurfer.registerPlugin(RegionsPlugin.create())); _wavesurfer.toggleInteraction(true); setWavesurfer(_wavesurfer); return () => { _wavesurfer.destroy(); setIsPlaying(false); setCurrentTime(0); }; } }, []); useEffect(() => { if (!wavesurfer) return; if (!props.media) return; wavesurfer.setMediaElement(props.media); }, [props.media, wavesurfer]); useEffect(() => { if (!waveRegions) return; topicsRef.current = props.topics; if (firstRender) { setFirstRender(false); // wait for the waveform to render, if you don't markers will be stacked on top of each other // I tried to listen for the waveform to be ready but it didn't work setTimeout(() => { renderMarkers(); }, 300); } else { renderMarkers(); } }, [props.topics, waveRegions]); const renderMarkers = () => { if (!waveRegions) return; waveRegions.clearRegions(); for (let topic of topicsRef.current) { const content = document.createElement("div"); content.setAttribute("style", waveSurferStyles.marker); content.onmouseover = (e) => { content.style.backgroundColor = waveSurferStyles.markerHover.backgroundColor; content.style.width = "300px"; if (content.parentElement) { content.parentElement.style.zIndex = "999"; } }; content.onmouseout = () => { content.setAttribute("style", waveSurferStyles.marker); if (content.parentElement) { content.parentElement.style.zIndex = "0"; } }; content.textContent = topic.title; const region = waveRegions.addRegion({ start: topic.timestamp, content, drag: false, resize: false, top: 0, }); region.on("click", (e) => { e.stopPropagation(); setActiveTopic(topic); wavesurfer?.setTime(region.start); }); } }; useEffect(() => { if (activeTopic) { wavesurfer?.setTime(activeTopic.timestamp); } }, [activeTopic]); const handlePlayClick = () => { wavesurfer?.playPause(); }; const timeLabel = () => { if (props.mediaDuration && Math.floor(props.mediaDuration / 1000) > 0) return `${formatTime(currentTime)}/${formatTimeMs(props.mediaDuration)}`; return ""; }; return ( {isPlaying ? : } {timeLabel()} ); }