From 0eb670ca196316f4bf2778647fc5518ec27b565a Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 17 Jul 2025 10:04:59 -0600 Subject: [PATCH] fix: don't attempt to load waveform/mp3 if audio was deleted (#495) --- .../(app)/transcripts/[transcriptId]/page.tsx | 77 ++++++++-------- www/app/(app)/transcripts/topicList.tsx | 10 ++- www/app/(app)/transcripts/useMp3.ts | 87 ++++++++++--------- www/app/(app)/transcripts/useWaveform.ts | 21 ++--- 4 files changed, 107 insertions(+), 88 deletions(-) diff --git a/www/app/(app)/transcripts/[transcriptId]/page.tsx b/www/app/(app)/transcripts/[transcriptId]/page.tsx index f2716178..dddd1470 100644 --- a/www/app/(app)/transcripts/[transcriptId]/page.tsx +++ b/www/app/(app)/transcripts/[transcriptId]/page.tsx @@ -12,7 +12,7 @@ import FinalSummary from "./finalSummary"; import TranscriptTitle from "../transcriptTitle"; import Player from "../player"; import { useRouter } from "next/navigation"; -import { Flex, Grid, GridItem, Skeleton, Text } from "@chakra-ui/react"; +import { Box, Flex, Grid, GridItem, Skeleton, Text } from "@chakra-ui/react"; type TranscriptDetails = { params: { @@ -29,10 +29,13 @@ export default function TranscriptDetails(details: TranscriptDetails) { const transcriptStatus = transcript.response?.status; const waiting = statusToRedirect.includes(transcriptStatus || ""); - const topics = useTopics(transcriptId); - const waveform = useWaveform(transcriptId, waiting); - const useActiveTopic = useState(null); const mp3 = useMp3(transcriptId, waiting); + const topics = useTopics(transcriptId); + const waveform = useWaveform( + transcriptId, + waiting || mp3.loading || mp3.audioDeleted === true, + ); + const useActiveTopic = useState(null); useEffect(() => { if (waiting) { @@ -76,23 +79,24 @@ export default function TranscriptDetails(details: TranscriptDetails) { mt={4} mb={4} > - {waveform.waveform && - mp3.media && - !mp3.audioDeleted && - topics.topics ? ( - - ) : waveform.error ? ( -
error loading this recording
- ) : mp3.audioDeleted ? ( -
Audio was deleted
- ) : ( - + {!mp3.audioDeleted && ( + <> + {waveform.waveform && mp3.media && topics.topics ? ( + + ) : !mp3.loading && (waveform.error || mp3.error) ? ( + + Error loading this recording + + ) : ( + + )} + )} - - { - transcript.reload(); - }} - /> + + + + { + transcript.reload(); + }} + /> + + {mp3.audioDeleted && ( + + No audio is available because one or more participants didn't + consent to keep the audio + + )} + { - if (activeTopic) scrollToTopic(); - }, [activeTopic]); + if (activeTopic && autoscroll) scrollToTopic(); + }, [activeTopic, autoscroll]); // scroll top is not rounded, heights are, so exact match won't work. // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled @@ -105,8 +105,10 @@ export function TopicList({ const requireLogin = featureEnabled("requireLogin"); useEffect(() => { - setActiveTopic(topics[topics.length - 1]); - }, [topics]); + if (autoscroll) { + setActiveTopic(topics[topics.length - 1]); + } + }, [topics, autoscroll]); return ( { useEffect(() => { if (!transcriptId || !api || later) return; - let deleted: boolean | null = null; + let stopped = false; + let audioElement: HTMLAudioElement | null = null; + let handleCanPlay: (() => void) | null = null; + let handleError: (() => void) | null = null; setTranscriptMetadataLoading(true); - - const audioElement = document.createElement("audio"); - audioElement.src = `${api_url}/v1/transcripts/${transcriptId}/audio/mp3`; - audioElement.crossOrigin = "anonymous"; - audioElement.preload = "auto"; - - const handleCanPlay = () => { - if (deleted) { - console.error( - "Illegal state: audio supposed to be deleted, but was loaded", - ); - return; - } - setAudioLoading(false); - setAudioLoadingError(null); - }; - - const handleError = () => { - setAudioLoading(false); - if (deleted) { - // we arrived here earlier, ignore - return; - } - setAudioLoadingError("Failed to load audio"); - }; - - audioElement.addEventListener("canplay", handleCanPlay); - audioElement.addEventListener("error", handleError); - - setMedia(audioElement); - setAudioLoading(true); - let stopped = false; - // Fetch transcript info in parallel + // First fetch transcript info to check if audio is deleted api .v1TranscriptGet({ transcriptId }) .then((transcript) => { - if (stopped) return; - deleted = transcript.audio_deleted || false; + if (stopped) { + return; + } + + const deleted = transcript.audio_deleted || false; setAudioDeleted(deleted); setTranscriptMetadataLoadingError(null); + if (deleted) { + // Audio is deleted, don't attempt to load it setMedia(null); setAudioLoadingError(null); + setAudioLoading(false); + return; + } + + // Audio is not deleted, proceed to load it + audioElement = document.createElement("audio"); + audioElement.src = `${api_url}/v1/transcripts/${transcriptId}/audio/mp3`; + audioElement.crossOrigin = "anonymous"; + audioElement.preload = "auto"; + + handleCanPlay = () => { + if (stopped) return; + setAudioLoading(false); + setAudioLoadingError(null); + }; + + handleError = () => { + if (stopped) return; + setAudioLoading(false); + setAudioLoadingError("Failed to load audio"); + }; + + audioElement.addEventListener("canplay", handleCanPlay); + audioElement.addEventListener("error", handleError); + + if (!stopped) { + setMedia(audioElement); } - // if deleted, media will or already returned error }) .catch((error) => { if (stopped) return; console.error("Failed to fetch transcript:", error); setAudioDeleted(null); setTranscriptMetadataLoadingError(error.message); + setAudioLoading(false); }) .finally(() => { if (stopped) return; @@ -118,10 +121,14 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => { return () => { stopped = true; - audioElement.removeEventListener("canplay", handleCanPlay); - audioElement.removeEventListener("error", handleError); + if (audioElement) { + audioElement.src = ""; + if (handleCanPlay) + audioElement.removeEventListener("canplay", handleCanPlay); + if (handleError) audioElement.removeEventListener("error", handleError); + } }; - }, [transcriptId, !api, later, api_url]); + }, [transcriptId, api, later, api_url]); const getNow = () => { setLater(false); diff --git a/www/app/(app)/transcripts/useWaveform.ts b/www/app/(app)/transcripts/useWaveform.ts index 9139a2fb..19b2a265 100644 --- a/www/app/(app)/transcripts/useWaveform.ts +++ b/www/app/(app)/transcripts/useWaveform.ts @@ -10,16 +10,22 @@ type AudioWaveFormResponse = { error: Error | null; }; -const useWaveform = (id: string, waiting: boolean): AudioWaveFormResponse => { +const useWaveform = (id: string, skip: boolean): AudioWaveFormResponse => { const [waveform, setWaveform] = useState(null); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); const [error, setErrorState] = useState(null); const { setError } = useError(); const api = useApi(); useEffect(() => { - if (!id || !api || waiting) return; + if (!id || !api || skip) { + setLoading(false); + setErrorState(null); + setWaveform(null); + return; + } setLoading(true); + setErrorState(null); api .v1TranscriptGetAudioWaveform({ transcriptId: id }) .then((result) => { @@ -29,14 +35,9 @@ const useWaveform = (id: string, waiting: boolean): AudioWaveFormResponse => { }) .catch((err) => { setErrorState(err); - const shouldShowHuman = shouldShowError(err); - if (shouldShowHuman) { - setError(err, "There was an error loading the waveform"); - } else { - setError(err); - } + setLoading(false); }); - }, [id, !api, waiting]); + }, [id, api, skip]); return { waveform, loading, error }; };