diff --git a/www/app/[domain]/transcripts/[transcriptId]/page.tsx b/www/app/[domain]/transcripts/[transcriptId]/page.tsx
index 56201c3c..bebfe261 100644
--- a/www/app/[domain]/transcripts/[transcriptId]/page.tsx
+++ b/www/app/[domain]/transcripts/[transcriptId]/page.tsx
@@ -5,7 +5,6 @@ import useTopics from "../useTopics";
import useWaveform from "../useWaveform";
import useMp3 from "../useMp3";
import { TopicList } from "../topicList";
-import Recorder from "../recorder";
import { Topic } from "../webSocketTypes";
import React, { useState } from "react";
import "../../../styles/button.css";
@@ -13,6 +12,7 @@ import FinalSummary from "../finalSummary";
import ShareLink from "../shareLink";
import QRCode from "react-qr-code";
import TranscriptTitle from "../transcriptTitle";
+import Player from "../player";
type TranscriptDetails = {
params: {
@@ -62,14 +62,12 @@ export default function TranscriptDetails(details: TranscriptDetails) {
/>
)}
{!waveform?.loading && (
-
)}
diff --git a/www/app/[domain]/transcripts/player.tsx b/www/app/[domain]/transcripts/player.tsx
new file mode 100644
index 00000000..6143836e
--- /dev/null
+++ b/www/app/[domain]/transcripts/player.tsx
@@ -0,0 +1,167 @@
+import React, { useRef, useEffect, useState } from "react";
+
+import WaveSurfer from "wavesurfer.js";
+import CustomRegionsPlugin from "../../lib/custom-plugins/regions";
+
+import { formatTime } from "../../lib/time";
+import { Topic } from "./webSocketTypes";
+import { AudioWaveform } from "../../api";
+import { waveSurferStyles } from "../../styles/recorder";
+
+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);
+
+ // Waveform setup
+ useEffect(() => {
+ if (waveformRef.current) {
+ // XXX duration is required to prevent recomputing peaks from audio
+ // However, the current waveform returns only the peaks, and no duration
+ // And the backend does not save duration properly.
+ // So at the moment, we deduct the duration from the topics.
+ // This is not ideal, but it works for now.
+ const _wavesurfer = WaveSurfer.create({
+ container: waveformRef.current,
+ peaks: props.waveform.data,
+ hideScrollbar: true,
+ autoCenter: true,
+ barWidth: 2,
+ height: "auto",
+ duration: props.mediaDuration,
+
+ ...waveSurferStyles.player,
+ });
+
+ // 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(CustomRegionsPlugin.create()));
+
+ _wavesurfer.toggleInteraction(true);
+
+ _wavesurfer.setMediaElement(props.media);
+
+ 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(() => {
+ topicsRef.current = props.topics;
+ 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 = () => {
+ content.style.backgroundColor =
+ waveSurferStyles.markerHover.backgroundColor;
+ content.style.zIndex = "999";
+ content.style.width = "300px";
+ };
+ content.onmouseout = () => {
+ content.setAttribute("style", waveSurferStyles.marker);
+ };
+ content.textContent = topic.title;
+
+ const region = waveRegions.addRegion({
+ start: topic.timestamp,
+ content,
+ color: "f00",
+ drag: false,
+ });
+ 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)
+ return `${formatTime(currentTime)}/${formatTime(props.mediaDuration)}`;
+ return "";
+ };
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/www/app/[domain]/transcripts/recorder.tsx b/www/app/[domain]/transcripts/recorder.tsx
index 8db32ff7..5b4420c4 100644
--- a/www/app/[domain]/transcripts/recorder.tsx
+++ b/www/app/[domain]/transcripts/recorder.tsx
@@ -6,11 +6,8 @@ import CustomRegionsPlugin from "../../lib/custom-plugins/regions";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faMicrophone } from "@fortawesome/free-solid-svg-icons";
-import { faDownload } from "@fortawesome/free-solid-svg-icons";
import { formatTime } from "../../lib/time";
-import { Topic } from "./webSocketTypes";
-import { AudioWaveform } from "../../api";
import AudioInputsDropdown from "./audioInputsDropdown";
import { Option } from "react-dropdown";
import { waveSurferStyles } from "../../styles/recorder";
@@ -19,17 +16,8 @@ import { useError } from "../../(errors)/errorContext";
type RecorderProps = {
setStream?: React.Dispatch>;
onStop?: () => void;
- topics: Topic[];
getAudioStream?: (deviceId) => Promise;
audioDevices?: Option[];
- useActiveTopic: [
- Topic | null,
- React.Dispatch>,
- ];
- waveform?: AudioWaveform | null;
- isPastMeeting: boolean;
- transcriptId?: string | null;
- media?: HTMLMediaElement | null;
mediaDuration?: number | null;
};
@@ -38,7 +26,7 @@ export default function Recorder(props: RecorderProps) {
const [wavesurfer, setWavesurfer] = useState(null);
const [record, setRecord] = useState(null);
const [isRecording, setIsRecording] = useState(false);
- const [hasRecorded, setHasRecorded] = useState(props.isPastMeeting);
+ const [hasRecorded, setHasRecorded] = useState(false);
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [timeInterval, setTimeInterval] = useState(null);
@@ -48,8 +36,6 @@ export default function Recorder(props: RecorderProps) {
);
const [deviceId, setDeviceId] = useState(null);
const [recordStarted, setRecordStarted] = useState(false);
- const [activeTopic, setActiveTopic] = props.useActiveTopic;
- const topicsRef = useRef(props.topics);
const [showDevices, setShowDevices] = useState(false);
const { setError } = useError();
@@ -73,8 +59,6 @@ export default function Recorder(props: RecorderProps) {
if (!record.isRecording()) return;
handleRecClick();
break;
- case "^":
- throw new Error("Unhandled Exception thrown by '^' shortcut");
case "(":
location.href = "/login";
break;
@@ -104,14 +88,8 @@ export default function Recorder(props: RecorderProps) {
// Waveform setup
useEffect(() => {
if (waveformRef.current) {
- // XXX duration is required to prevent recomputing peaks from audio
- // However, the current waveform returns only the peaks, and no duration
- // And the backend does not save duration properly.
- // So at the moment, we deduct the duration from the topics.
- // This is not ideal, but it works for now.
const _wavesurfer = WaveSurfer.create({
container: waveformRef.current,
- peaks: props.waveform?.data,
hideScrollbar: true,
autoCenter: true,
barWidth: 2,
@@ -121,10 +99,8 @@ export default function Recorder(props: RecorderProps) {
...waveSurferStyles.player,
});
- if (!props.transcriptId) {
- const _wshack: any = _wavesurfer;
- _wshack.renderer.renderSingleCanvas = () => {};
- }
+ const _wshack: any = _wavesurfer;
+ _wshack.renderer.renderSingleCanvas = () => {};
// styling
const wsWrapper = _wavesurfer.getWrapper();
@@ -144,12 +120,6 @@ export default function Recorder(props: RecorderProps) {
setRecord(_wavesurfer.registerPlugin(RecordPlugin.create()));
setWaveRegions(_wavesurfer.registerPlugin(CustomRegionsPlugin.create()));
- if (props.isPastMeeting) _wavesurfer.toggleInteraction(true);
-
- if (props.media) {
- _wavesurfer.setMediaElement(props.media);
- }
-
setWavesurfer(_wavesurfer);
return () => {
@@ -161,58 +131,6 @@ export default function Recorder(props: RecorderProps) {
}
}, []);
- useEffect(() => {
- if (!wavesurfer) return;
- if (!props.media) return;
- wavesurfer.setMediaElement(props.media);
- }, [props.media, wavesurfer]);
-
- useEffect(() => {
- topicsRef.current = props.topics;
- if (!isRecording) 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 = () => {
- content.style.backgroundColor =
- waveSurferStyles.markerHover.backgroundColor;
- content.style.zIndex = "999";
- content.style.width = "300px";
- };
- content.onmouseout = () => {
- content.setAttribute("style", waveSurferStyles.marker);
- };
- content.textContent = topic.title;
-
- const region = waveRegions.addRegion({
- start: topic.timestamp,
- content,
- color: "f00",
- drag: false,
- });
- region.on("click", (e) => {
- e.stopPropagation();
- setActiveTopic(topic);
- wavesurfer?.setTime(region.start);
- });
- }
- };
-
- useEffect(() => {
- if (!record) return;
-
- return record.on("stopRecording", () => {
- renderMarkers();
- });
- }, [record]);
-
useEffect(() => {
if (isRecording) {
const interval = window.setInterval(() => {
@@ -229,12 +147,6 @@ export default function Recorder(props: RecorderProps) {
}
}, [isRecording]);
- useEffect(() => {
- if (activeTopic) {
- wavesurfer?.setTime(activeTopic.timestamp);
- }
- }, [activeTopic]);
-
const handleRecClick = async () => {
if (!record) return console.log("no record");
@@ -320,7 +232,6 @@ export default function Recorder(props: RecorderProps) {
if (!record) return;
if (!destinationStream) return;
if (props.setStream) props.setStream(destinationStream);
- waveRegions?.clearRegions();
if (destinationStream) {
record.startRecording(destinationStream);
setIsRecording(true);
@@ -379,23 +290,9 @@ export default function Recorder(props: RecorderProps) {
} text-white ml-2 md:ml:4 md:h-[78px] md:min-w-[100px] text-lg`}
id="play-btn"
onClick={handlePlayClick}
- disabled={isRecording}
>
{isPlaying ? "Pause" : "Play"}
-
- {props.transcriptId && (
-
-
-
- )}
>
)}
{!hasRecorded && (