From 78047946e5f40507a8de029198da3f1a74b8205d Mon Sep 17 00:00:00 2001
From: Saelmala <sandra.larsson@eyevinn.se>
Date: Mon, 9 Dec 2024 11:28:26 +0100
Subject: [PATCH] fix: remove duplicate outputs

---
 .../production-line/production-line.tsx       |  12 +-
 .../production-line/use-control-volume.tsx    |  50 ++------
 .../production-line/use-rtc-connection.ts     | 111 ++++++++++++------
 3 files changed, 89 insertions(+), 84 deletions(-)

diff --git a/src/components/production-line/production-line.tsx b/src/components/production-line/production-line.tsx
index 0d34f4a2..7ae37216 100644
--- a/src/components/production-line/production-line.tsx
+++ b/src/components/production-line/production-line.tsx
@@ -146,10 +146,6 @@ export const ProductionLine: FC = () => {
     inputId: joinProductionOptions?.audioinput ?? null,
   });
 
-  const { setVolume } = useControlVolume({
-    stream: inputAudioStream !== "no-device" ? inputAudioStream : null,
-  });
-
   const muteInput = useCallback(
     (mute: boolean) => {
       if (inputAudioStream && inputAudioStream !== "no-device") {
@@ -192,6 +188,10 @@ export const ProductionLine: FC = () => {
     sessionId,
   });
 
+  const { setVolume } = useControlVolume({
+    audioElements,
+  });
+
   useEffect(() => {
     if (connectionState === "connected") {
       playEnterSound();
@@ -199,9 +199,9 @@ export const ProductionLine: FC = () => {
   }, [connectionState, playEnterSound]);
 
   const muteOutput = useCallback(() => {
-    audioElements.forEach((singleElement: HTMLAudioElement) => {
+    audioElements.forEach(({ gainNode }) => {
       // eslint-disable-next-line no-param-reassign
-      singleElement.muted = !isOutputMuted;
+      gainNode.gain.value = isOutputMuted ? 1 : 0;
     });
     setIsOutputMuted(!isOutputMuted);
   }, [audioElements, isOutputMuted]);
diff --git a/src/components/production-line/use-control-volume.tsx b/src/components/production-line/use-control-volume.tsx
index d47ad2a4..4706cf60 100644
--- a/src/components/production-line/use-control-volume.tsx
+++ b/src/components/production-line/use-control-volume.tsx
@@ -1,54 +1,26 @@
-import { useEffect, useRef, useState } from "react";
+type TAudioElement = {
+  audioCtx: AudioContext;
+  gainNode: GainNode;
+  source: MediaStreamAudioSourceNode;
+};
 
 type TUseControlVolumeOptions = {
-  stream: MediaStream | null;
+  audioElements: TAudioElement[];
 };
 
 type TUseControlVolume = (options: TUseControlVolumeOptions) => {
   setVolume: (value: number) => void;
-  audioContext: AudioContext | null;
 };
 
-export const useControlVolume: TUseControlVolume = ({ stream }) => {
-  const [audioContext, setAudioContext] = useState<AudioContext | null>(null);
-  const gainNodeRef = useRef<GainNode | null>(null);
-
-  useEffect(() => {
-    if (!stream) return;
-
-    console.log("Initializing Audio Context");
-
-    const audioCtx = new AudioContext();
-    setAudioContext(audioCtx);
-
-    const source = audioCtx.createMediaStreamSource(stream);
-
-    const gainNode = audioCtx.createGain();
-    gainNode.gain.value = 0.5;
-
-    source.connect(gainNode);
-    // Outputs the audio to the speakers
-    gainNode.connect(audioCtx.destination);
-
-    gainNodeRef.current = gainNode;
-
-    // eslint-disable-next-line consistent-return
-    return () => {
-      source.disconnect();
-      gainNode.disconnect();
-      audioCtx.close();
-      gainNodeRef.current = null;
-    };
-  }, [stream]);
-
+export const useControlVolume: TUseControlVolume = ({ audioElements }) => {
   const setVolume = (value: number) => {
-    if (gainNodeRef.current) {
+    audioElements.forEach(({ gainNode }) => {
       const clampedValue = Math.max(0, Math.min(1, value));
-      gainNodeRef.current.gain.value = clampedValue;
+      gainNode.gain.setValueAtTime(clampedValue, gainNode.context.currentTime);
 
       console.log("Setting Gain Node Volume:", clampedValue);
-    }
+    });
   };
 
-  return { setVolume, audioContext };
+  return { setVolume };
 };
diff --git a/src/components/production-line/use-rtc-connection.ts b/src/components/production-line/use-rtc-connection.ts
index 119ea61c..446497a1 100644
--- a/src/components/production-line/use-rtc-connection.ts
+++ b/src/components/production-line/use-rtc-connection.ts
@@ -29,7 +29,15 @@ type TEstablishConnection = {
   joinProductionOptions: TJoinProductionOptions;
   sessionId: string;
   dispatch: Dispatch<TGlobalStateAction>;
-  setAudioElements: Dispatch<SetStateAction<HTMLAudioElement[]>>;
+  setAudioElements: Dispatch<
+    SetStateAction<
+      {
+        audioCtx: AudioContext;
+        gainNode: GainNode;
+        source: MediaStreamAudioSourceNode;
+      }[]
+    >
+  >;
   setNoStreamError: (input: boolean) => void;
 };
 
@@ -49,7 +57,7 @@ const attachInputAudioToPeerConnection = ({
 const establishConnection = ({
   rtcPeerConnection,
   sdpOffer,
-  joinProductionOptions,
+  // joinProductionOptions,
   sessionId,
   dispatch,
   setAudioElements,
@@ -60,31 +68,45 @@ const establishConnection = ({
     const selectedStream = streams[0];
 
     if (selectedStream && selectedStream.getAudioTracks().length !== 0) {
-      const audioElement = new Audio();
-
-      audioElement.controls = false;
-      audioElement.autoplay = true;
-      audioElement.onerror = () => {
-        dispatch({
-          type: "ERROR",
-          payload: new Error(
-            `Audio Error: ${audioElement.error?.code} - ${audioElement.error?.message}`
-          ),
-        });
-      };
-
-      audioElement.srcObject = selectedStream;
-
-      setAudioElements((prevArray) => [audioElement, ...prevArray]);
-      if (joinProductionOptions.audiooutput) {
-        audioElement.setSinkId(joinProductionOptions.audiooutput).catch((e) => {
-          dispatch({
-            type: "ERROR",
-            payload:
-              e instanceof Error ? e : new Error("Error assigning audio sink."),
-          });
-        });
-      }
+      const audioCtx = new AudioContext();
+      const gainNode = audioCtx.createGain();
+      const source = audioCtx.createMediaStreamSource(selectedStream);
+
+      source.connect(gainNode);
+      gainNode.connect(audioCtx.destination);
+
+      gainNode.gain.value = 0.5;
+
+      setAudioElements((prevArray) => [
+        { audioCtx, gainNode, source },
+        ...prevArray,
+      ]);
+
+      // const audioElement = new Audio();
+
+      // audioElement.controls = false;
+      // audioElement.autoplay = true;
+      // audioElement.onerror = () => {
+      //   dispatch({
+      //     type: "ERROR",
+      //     payload: new Error(
+      //       `Audio Error: ${audioElement.error?.code} - ${audioElement.error?.message}`
+      //     ),
+      //   });
+      // };
+
+      // audioElement.srcObject = selectedStream;
+
+      // setAudioElements((prevArray) => [audioElement, ...prevArray]);
+      // if (joinProductionOptions.audiooutput) {
+      //   audioElement.setSinkId(joinProductionOptions.audiooutput).catch((e) => {
+      //     dispatch({
+      //       type: "ERROR",
+      //       payload:
+      //         e instanceof Error ? e : new Error("Error assigning audio sink."),
+      //     });
+      //   });
+      // }
     } else if (selectedStream && selectedStream.getAudioTracks().length === 0) {
       setNoStreamError(true);
       dispatch({
@@ -232,15 +254,24 @@ export const useRtcConnection = ({
   const [, dispatch] = useGlobalState();
   const [connectionState, setConnectionState] =
     useState<RTCPeerConnectionState | null>(null);
-  const [audioElements, setAudioElements] = useState<HTMLAudioElement[]>([]);
+  const [audioElements, setAudioElements] = useState<
+    {
+      audioCtx: AudioContext;
+      gainNode: GainNode;
+      source: MediaStreamAudioSourceNode;
+    }[]
+  >([]);
   const [noStreamError, setNoStreamError] = useState(false);
-  const audioElementsRef = useRef<HTMLAudioElement[]>(audioElements);
+  const audioElementsRef = useRef<
+    {
+      audioCtx: AudioContext;
+      gainNode: GainNode;
+      source: MediaStreamAudioSourceNode;
+    }[]
+  >(audioElements);
   const navigate = useNavigate();
-  const { setVolume, audioContext } = useControlVolume({
-    stream:
-      inputAudioStream && typeof inputAudioStream !== "string"
-        ? inputAudioStream
-        : null,
+  const { setVolume } = useControlVolume({
+    audioElements,
   });
 
   // Use a ref to make sure we only clean up
@@ -251,10 +282,12 @@ export const useRtcConnection = ({
   }, [audioElements]);
 
   const cleanUpAudio = useCallback(() => {
-    audioElementsRef.current.forEach((el) => {
-      el.pause();
-      // eslint-disable-next-line no-param-reassign
-      el.srcObject = null;
+    audioElementsRef.current.forEach(({ audioCtx, source, gainNode }) => {
+      // el.pause();
+      // el.srcObject = null;
+      source.disconnect();
+      gainNode.disconnect();
+      audioCtx.close();
     });
   }, [audioElementsRef]);
 
@@ -405,5 +438,5 @@ export const useRtcConnection = ({
     };
   }, [rtcPeerConnection]);
 
-  return { connectionState, audioElements, setVolume, audioContext };
+  return { connectionState, audioElements, setVolume };
 };