Skip to content

Commit

Permalink
feat: control volume of line
Browse files Browse the repository at this point in the history
  • Loading branch information
Saelmala committed Dec 9, 2024
1 parent 90dcbd4 commit db21327
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/assets/icons/full_sound.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/assets/icons/icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import ConfirmSvg from "./done.svg?react";
import StepLeftSvg from "./chevron_left.svg?react";
import StepRightSvg from "./navigate_next.svg?react";
import Settings from "./settings.svg?react";
import NoSound from "./no_sound.svg?react";
import FullSound from "./full_sound.svg?react";

export const MicMuted = () => <MicMute />;

Expand All @@ -31,3 +33,7 @@ export const StepLeftIcon = () => <StepLeftSvg />;
export const StepRightIcon = () => <StepRightSvg />;

export const SettingsIcon = () => <Settings />;

export const NoSoundIcon = () => <NoSound />;

export const FullSoundIcon = () => <FullSound />;
1 change: 1 addition & 0 deletions src/assets/icons/no_sound.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions src/components/production-line/production-line.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ import { useLinePolling } from "./use-line-polling.ts";
import { useFetchProduction } from "../landing-page/use-fetch-production.ts";
import { useIsLoading } from "./use-is-loading.ts";
import { useCheckBadLineData } from "./use-check-bad-line-data.ts";
import { useControlVolume } from "./use-control-volume.tsx";
import { NavigateToRootButton } from "../navigate-to-root-button/navigate-to-root-button.tsx";
import { useAudioCue } from "./use-audio-cue.ts";
import { DisplayWarning } from "../display-box.tsx";
import { SettingsModal, Hotkeys } from "./settings-modal.tsx";
import { Slider } from "../slider/slider.tsx";

const TempDiv = styled.div`
padding: 0 0 2rem 0;
Expand Down Expand Up @@ -144,6 +146,10 @@ 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") {
Expand Down Expand Up @@ -312,6 +318,13 @@ export const ProductionLine: FC = () => {
<DisplayContainerHeader>Controls</DisplayContainerHeader>

<FlexContainer>
<Slider
min={0}
max={100}
initialValue={50}
step={5}
setVolume={setVolume}
/>
<FlexButtonWrapper>
<UserControlBtn type="button" onClick={() => muteOutput()}>
<ButtonIcon>
Expand Down
54 changes: 54 additions & 0 deletions src/components/production-line/use-control-volume.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useEffect, useRef, useState } from "react";

type TUseControlVolumeOptions = {
stream: MediaStream | null;
};

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]);

const setVolume = (value: number) => {
if (gainNodeRef.current) {
const clampedValue = Math.max(0, Math.min(1, value));
gainNodeRef.current.gain.value = clampedValue;

console.log("Setting Gain Node Volume:", clampedValue);
}
};

return { setVolume, audioContext };
};
9 changes: 8 additions & 1 deletion src/components/production-line/use-rtc-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useGlobalState } from "../../global-state/context-provider.tsx";
import { TGlobalStateAction } from "../../global-state/global-state-actions.ts";
import { TUseAudioInputValues } from "./use-audio-input.ts";
import { startRtcStatInterval } from "./rtc-stat-interval.ts";
import { useControlVolume } from "./use-control-volume.tsx";

type TRtcConnectionOptions = {
inputAudioStream: TUseAudioInputValues;
Expand Down Expand Up @@ -235,6 +236,12 @@ export const useRtcConnection = ({
const [noStreamError, setNoStreamError] = useState(false);
const audioElementsRef = useRef<HTMLAudioElement[]>(audioElements);
const navigate = useNavigate();
const { setVolume, audioContext } = useControlVolume({
stream:
inputAudioStream && typeof inputAudioStream !== "string"
? inputAudioStream
: null,
});

// Use a ref to make sure we only clean up
// audio elements once, and not every time
Expand Down Expand Up @@ -398,5 +405,5 @@ export const useRtcConnection = ({
};
}, [rtcPeerConnection]);

return { connectionState, audioElements };
return { connectionState, audioElements, setVolume, audioContext };
};
97 changes: 97 additions & 0 deletions src/components/slider/slider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import styled from "@emotion/styled";
import { FC, useState } from "react";
import { NoSoundIcon, FullSoundIcon } from "../../assets/icons/icon";

const SliderWrapper = styled.div`
width: 100%;
margin: 2rem 0;
display: flex;
flex-direction: row;
align-items: center;
position: relative;
`;

const IconWrapper = styled.div`
width: 5rem;
height: 5rem;
padding: 0.5rem;
`;

const SliderTrack = styled.div`
width: 80%;
height: 0.4rem;
background-color: #e0e0e0;
border-radius: 0.2rem;
position: relative;
`;

const SliderThumb = styled.div<{ position: number }>`
width: 1.5rem;
height: 1.5rem;
background-color: #59cbe8;
border-radius: 50%;
position: absolute;
top: -0.6rem;
left: ${({ position }) => `${position}%`};
transform: translateX(-50%);
cursor: pointer;
`;

type SliderProps = {
min: number;
max: number;
step?: number;
initialValue?: number;
setVolume: (value: number) => void;
};

export const Slider: FC<SliderProps> = ({
min,
max,
step = 1,
initialValue = min,
setVolume,
}) => {
const [value, setValue] = useState(initialValue);

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = Number(e.target.value);
setValue(newValue);
setVolume(newValue / max);

console.log("Slider value: ", newValue);
console.log("Normalized value: ", newValue / max);
};

const thumbPosition = ((value - min) / (max - min)) * 100;

return (
<SliderWrapper>
<IconWrapper>
<NoSoundIcon />
</IconWrapper>
<SliderTrack>
<SliderThumb position={thumbPosition} />
<input
type="range"
min={min}
max={max}
step={step}
value={value}
onChange={handleInputChange}
style={{
width: "100%",
position: "absolute",
top: 0,
height: "0.4rem",
opacity: 0,
pointerEvents: "all",
}}
/>
</SliderTrack>
<IconWrapper>
<FullSoundIcon />
</IconWrapper>
</SliderWrapper>
);
};

0 comments on commit db21327

Please sign in to comment.