diff --git a/src/assets/icons/icon.tsx b/src/assets/icons/icon.tsx new file mode 100644 index 00000000..e84bf61a --- /dev/null +++ b/src/assets/icons/icon.tsx @@ -0,0 +1,12 @@ +import styled from "@emotion/styled"; +import MicMute from "./mic_off.svg"; +import MicUnmute from "./mic_on.svg"; + +const Icon = styled.img` + width: 8rem; + padding-right: 1em; +`; + +export const MicMuted = () => <Icon src={MicMute} alt="off-microphone" />; + +export const MicUnmuted = () => <Icon src={MicUnmute} alt="on-microphone" />; diff --git a/src/assets/icons/mic_off.svg b/src/assets/icons/mic_off.svg new file mode 100644 index 00000000..3b7b7b8a --- /dev/null +++ b/src/assets/icons/mic_off.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#c05151"> + <path d="M0 0h24v24H0V0z" fill="none" /> + <path + d="M10.8 4.9c0-.66.54-1.2 1.2-1.2s1.2.54 1.2 1.2l-.01 3.91L15 10.6V5c0-1.66-1.34-3-3-3-1.54 0-2.79 1.16-2.96 2.65l1.76 1.76V4.9zM19 11h-1.7c0 .58-.1 1.13-.27 1.64l1.27 1.27c.44-.88.7-1.87.7-2.91zM4.41 2.86 3 4.27l6 6V11c0 1.66 1.34 3 3 3 .23 0 .44-.03.65-.08l1.66 1.66c-.71.33-1.5.52-2.31.52-2.76 0-5.3-2.1-5.3-5.1H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28a7.13 7.13 0 0 0 2.55-.9l4.2 4.2 1.41-1.41L4.41 2.86z" /> +</svg> \ No newline at end of file diff --git a/src/assets/icons/mic_on.svg b/src/assets/icons/mic_on.svg new file mode 100644 index 00000000..eafe0658 --- /dev/null +++ b/src/assets/icons/mic_on.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="#6fd84f" viewBox="0 0 24 24"> + <g fill="none"> + <path d="M0 0h24v24H0z" /> + <path d="M0 0h24v24H0z" /> + <path d="M0 0h24v24H0z" /> + </g> + <path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z" /> + <path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z" /> +</svg> \ No newline at end of file diff --git a/src/components/production-line/production-line.tsx b/src/components/production-line/production-line.tsx index dbb04072..6ea0672d 100644 --- a/src/components/production-line/production-line.tsx +++ b/src/components/production-line/production-line.tsx @@ -9,16 +9,25 @@ import { ActionButton } from "../landing-page/form-elements.tsx"; import { UserList } from "./user-list.tsx"; import { API } from "../../api/api.ts"; import { noop } from "../../helpers.ts"; +import { MicMuted, MicUnmuted } from "../../assets/icons/icon.tsx"; import { Spinner } from "../loader/loader.tsx"; const TempDiv = styled.div` padding: 1rem; `; + +const UserControlBtn = styled.button` + background-color: transparent; + border-color: transparent; +`; + export const ProductionLine: FC = () => { // const { productionId, lineId } = useParams(); - const [{ joinProductionOptions }, dispatch] = useGlobalState(); + const [{ joinProductionOptions, mediaStreamInput }, dispatch] = + useGlobalState(); const navigate = useNavigate(); const audioContainerRef = useRef<HTMLDivElement>(null); + const [micMute, setMicMute] = useState(true); const [participants, setParticipants] = useState< { name: string; sessionid: string }[] | null >(null); @@ -81,7 +90,15 @@ export const ProductionLine: FC = () => { navigate("/"); }; - // Mute/Unmute mic + useEffect(() => { + if (mediaStreamInput) { + mediaStreamInput.getTracks().forEach((track) => { + // eslint-disable-next-line no-param-reassign + track.enabled = !micMute; + }); + } + }, [mediaStreamInput, micMute]); + // Mute/Unmute speaker // Show active sink and mic // Exit button (link to /, clear production from state) @@ -95,6 +112,11 @@ export const ProductionLine: FC = () => { {!loading && ( <> <TempDiv>Production View</TempDiv> + <TempDiv> + <UserControlBtn type="button" onClick={() => setMicMute(!micMute)}> + {micMute ? <MicMuted /> : <MicUnmuted />} + </UserControlBtn> + </TempDiv> <TempDiv ref={audioContainerRef} /> {audioElements.length && ( <TempDiv>Incoming Audio Channels: {audioElements.length}</TempDiv> diff --git a/src/components/production-line/use-rtc-connection.ts b/src/components/production-line/use-rtc-connection.ts index 04fc2438..a19860b2 100644 --- a/src/components/production-line/use-rtc-connection.ts +++ b/src/components/production-line/use-rtc-connection.ts @@ -198,6 +198,11 @@ export const useRtcConnection = ({ rtcPeerConnection, inputAudioStream, }); + + dispatch({ + type: "CONNECTED_MEDIASTREAM", + payload: inputAudioStream, + }); } const { teardown } = establishConnection({ @@ -217,6 +222,11 @@ export const useRtcConnection = ({ onConnectionStateChange ); + dispatch({ + type: "CONNECTED_MEDIASTREAM", + payload: null, + }); + rtcPeerConnection.close(); }; }, [ diff --git a/src/global-state/global-state-actions.ts b/src/global-state/global-state-actions.ts index b81f5c55..53522e7b 100644 --- a/src/global-state/global-state-actions.ts +++ b/src/global-state/global-state-actions.ts @@ -5,7 +5,8 @@ export type TGlobalStateAction = | TProductionCreated | TProductionListFetched | TUpdateDevicesAction - | TUpdateJoinProductionOptions; + | TUpdateJoinProductionOptions + | TMediaStream; export type TPublishError = { type: "ERROR"; @@ -29,3 +30,8 @@ export type TUpdateJoinProductionOptions = { type: "UPDATE_JOIN_PRODUCTION_OPTIONS"; payload: TJoinProductionOptions | null; }; + +export type TMediaStream = { + type: "CONNECTED_MEDIASTREAM"; + payload: MediaStream | null; +}; diff --git a/src/global-state/global-state-reducer.ts b/src/global-state/global-state-reducer.ts index b397fbd2..ed1a95e8 100644 --- a/src/global-state/global-state-reducer.ts +++ b/src/global-state/global-state-reducer.ts @@ -9,6 +9,7 @@ const initialGlobalState: TGlobalState = { reloadProductionList: true, devices: null, joinProductionOptions: null, + mediaStreamInput: null, }; const globalReducer: Reducer<TGlobalState, TGlobalStateAction> = ( @@ -43,6 +44,11 @@ const globalReducer: Reducer<TGlobalState, TGlobalStateAction> = ( ...state, joinProductionOptions: action.payload, }; + case "CONNECTED_MEDIASTREAM": + return { + ...state, + mediaStreamInput: action.payload, + }; default: return state; } diff --git a/src/global-state/types.ts b/src/global-state/types.ts index acadd021..8eb0b69c 100644 --- a/src/global-state/types.ts +++ b/src/global-state/types.ts @@ -9,4 +9,5 @@ export type TGlobalState = { reloadProductionList: boolean; devices: MediaDeviceInfo[] | null; joinProductionOptions: TJoinProductionOptions | null; + mediaStreamInput: MediaStream | null; };