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 }; };