From 2f6165eab99710b977ef39ca60962992a1a31ffa Mon Sep 17 00:00:00 2001 From: Arjun Patel Date: Sat, 15 Jan 2022 21:51:21 -0800 Subject: [PATCH] adding devices and all of the little enhancements --- components/Dashboard/Header.tsx | 145 ++++++++++++++++++++++++++------ contexts/audioContext.tsx | 140 +++++++++++++++++++++++++----- 2 files changed, 240 insertions(+), 45 deletions(-) diff --git a/components/Dashboard/Header.tsx b/components/Dashboard/Header.tsx index aaa8193..4c7fc37 100644 --- a/components/Dashboard/Header.tsx +++ b/components/Dashboard/Header.tsx @@ -13,13 +13,15 @@ import { FaAngleDown, FaCheck, FaDatabase, + FaMicrophoneAltSlash, + FaVolumeMute, } from "react-icons/fa"; import { useTeamDashboardContext } from "../../contexts/teamDashboardContext"; import router from "next/router"; import Moment from "react-moment"; import { User } from "../../models/user"; import { generateGreetings } from "../../helpers/dateTime"; -import { Dropdown, Menu } from "antd"; +import { Dropdown, Menu, Tooltip } from "antd"; import { useRouter } from "next/router"; import { TeamMemberRole, TeamMemberStatus } from "../../models/teamMember"; import { toast } from "react-hot-toast"; @@ -28,6 +30,7 @@ import { useEffect, useState } from "react"; import TeamService from "../../services/teamService"; import { Team } from "../../models/team"; import Loading from "../Loading"; +import { useAudioContext } from "../../contexts/audioContext"; const teamService = new TeamService(); @@ -36,9 +39,25 @@ export default function Header() { const { team, user, userTeamMember } = useTeamDashboardContext(); const router = useRouter(); const { teamid } = router.query; + const { + hasRecPermit, + + audioInputDeviceId, + audioOutputDeviceId, + selectAudioOutput, + selectAudioInput, + inputDevices, + outputDevices, + + isMuted, + isSilenceMode, + muteOrUnmute, + silenceOrLivenMode, + } = useAudioContext(); const [usersTeams, setUserTeams] = useState(null); + // get team data for the team dropdown useEffect(() => { (async function () { try { @@ -79,6 +98,28 @@ export default function Header() { toast.error("You are not a team admin!"); } + const searchBar = ( +
+ + + + + +
+ ); + const TeamsMenu = ( }> @@ -130,28 +171,82 @@ export default function Header() { ); - const searchBar = ( -
- - - - - -
+ const audioInputDropMenu = ( + + {inputDevices.map((device, i) => { + return ( + : <>} + > + + + ); + })} + ); + const audioOutputDropMenu = ( + + {outputDevices.map((device, i) => { + return ( + : <>} + > + + + ); + })} + + ); + + function renderAudioInput() { + if (!hasRecPermit) { + return ( + + + + + + ); + } + + if (isMuted) { + return ( + + + + ); + } + + return ( + + + + + + ); + } + + function renderAudioOutput() { + if (isSilenceMode) { + return ( + + + + ); + } + + return ( + + + + + + ); + } + const today = new Date(); const periodOfDay = generateGreetings(); return ( @@ -180,9 +275,11 @@ export default function Header() { {/* search bar */} - - - + {/* */} + + {renderAudioOutput()} + + {renderAudioInput()} {/* teams menu */} diff --git a/contexts/audioContext.tsx b/contexts/audioContext.tsx index b9c5845..adf3420 100644 --- a/contexts/audioContext.tsx +++ b/contexts/audioContext.tsx @@ -5,7 +5,7 @@ import MicRecorder from "mic-recorder-to-mp3"; interface AudioContextInterface { selectedTeammate: string; // can only have one selected - setSelectedTeamMember: Function; + selectTeamMember: Function; teamShortcutMappings: {}; addTeamShortcutBinding: Function; @@ -18,14 +18,35 @@ interface AudioContextInterface { isMuted: Boolean; isSilenceMode: Boolean; // won't automatically listen to notifications or sounds + muteOrUnmute: Function; + silenceOrLivenMode: Function; hasRecPermit: Boolean; // permission to record or not + + audioInputDeviceId: string; + audioOutputDeviceId: string; + + selectAudioOutput: Function; + selectAudioInput: Function; + + inputDevices: MediaDeviceInfo[]; + outputDevices: MediaDeviceInfo[]; } const AudioContext = React.createContext(null); const Mp3Recorder = new MicRecorder({ bitRate: 128 }); +function stopBothVideoAndAudio(stream) { + stream.getTracks().forEach(function (track) { + if (track.readyState == "live") { + track.stop(); + + console.log("stopped playing anything"); + } + }); +} + export default function AudioContextProvider({ children }) { // SECTION: set up for shortcuts and recording and such const [selectedTeammate, setSelectedTeamMember] = useState(null); // id of selected teammate @@ -33,20 +54,99 @@ export default function AudioContextProvider({ children }) { const [hasRecPermit, setHasRecPermit] = useState(false); const [teamShortcutMappings, setTeamShortcutMappings] = useState<{}>(null); + const [audioInputDeviceId, setAudioInputDevice] = useState(null); // device id + const [audioOutputDeviceId, setAudioOutputDevice] = useState(null); // device id + + const [inputDevices, setInputDevices] = useState([]); + const [outputDevices, setOutputDevices] = useState([]); + + const [isMuted, setIsMuted] = useState(false); + const [isSilenceMode, setIseSilenceMode] = useState(false); + + const value: AudioContextInterface = { + selectedTeammate, + selectTeamMember, + addTeamShortcutBinding, + isRecording, + teamShortcutMappings, + + isMuted, + isSilenceMode, + muteOrUnmute, + silenceOrLivenMode, + + hasRecPermit, + + audioInputDeviceId, + audioOutputDeviceId, + selectAudioOutput, + selectAudioInput, + + inputDevices, + outputDevices, + }; + + function muteOrUnmute() { + setIsMuted((prevVal) => !prevVal); + } + + function silenceOrLivenMode() { + setIseSilenceMode((prevVal) => !prevVal); + } + + function selectAudioOutput(deviceId: string) { + setAudioOutputDevice(deviceId); + } + + function selectAudioInput(deviceId: string) { + setAudioInputDevice(deviceId); + } + + // set up audio + useEffect(() => { + (async function () { + try { + const devices = await navigator.mediaDevices.enumerateDevices(); + + const inputDevices: MediaDeviceInfo[] = devices.filter( + (d) => d.kind == "audioinput" + ); + + setAudioInputDevice(inputDevices ? inputDevices[0].deviceId : null); + setInputDevices(inputDevices); + + const outputDevices: MediaDeviceInfo[] = devices.filter( + (d) => d.kind == "audiooutput" + ); + + setAudioOutputDevice(outputDevices ? outputDevices[0].deviceId : null); + setOutputDevices(outputDevices); + console.log(devices); + } catch (e) { + console.log(e); + toast.error("Problem in setting up audio devices"); + } + })(); + }, []); useEffect(() => { navigator.mediaDevices - .getUserMedia({ audio: true }) - .then(() => { + .getUserMedia({ audio: { deviceId: audioInputDeviceId } }) + .then((stream) => { + // stop playing anything + stopBothVideoAndAudio(stream); + console.log("Permission Granted"); setHasRecPermit(true); }) - .catch(() => { + .catch((e) => { console.log("Permission Denied"); + toast.error("Please make sure that you have connected a microphone."); setHasRecPermit(false); }); - }, []); + }, [audioInputDeviceId]); // change it everytime we change the input device + // shortcut handlers need to be updated as the function has to have the fresh state useEffect(() => { document.addEventListener("keydown", handleKeyboardShortcut); document.addEventListener("keyup", handleKeyUp); @@ -55,10 +155,10 @@ export default function AudioContextProvider({ children }) { document.removeEventListener("keydown", handleKeyboardShortcut); document.removeEventListener("keyup", handleKeyUp); }; - }, [teamShortcutMappings]); + }, [teamShortcutMappings, selectedTeammate]); // SECTION: recording - function startRecording() { + async function startRecording() { if (!hasRecPermit) { toast.error("You did not allow recording permission!"); } else { @@ -73,7 +173,7 @@ export default function AudioContextProvider({ children }) { } // everything to do with audio file - function stopRecording() { + async function stopRecording() { Mp3Recorder.stop() .getMp3() .then(([buffer, blob]) => { @@ -95,13 +195,15 @@ export default function AudioContextProvider({ children }) { function handleKeyUp(event) { console.log("on key up"); + console.log(selectedTeammate); // use this to send the message to the right person + // if was recording and released R, then stop recording if (event.keyCode == KeyCode.R && selectedTeammate) { console.log("stopped recording"); setIsRecording(false); setSelectedTeamMember(null); - // stopRecording(); + stopRecording(); } } @@ -112,13 +214,16 @@ export default function AudioContextProvider({ children }) { console.log(event.keyCode); console.log(selectedTeammate); - console.log(teamShortcutMappings); // recording if (event.keyCode == KeyCode.R && selectedTeammate) { + if (!audioInputDeviceId) { + toast.error("No microphone selected"); + } + console.log("started recording"); setIsRecording(true); - // startRecording(); + startRecording(); } // if we have a valid user for such a shortcut, then go ahead...otherwise else if (teamShortcutMappings[event.keyCode]) { @@ -136,16 +241,9 @@ export default function AudioContextProvider({ children }) { setTeamShortcutMappings((prevMap) => ({ ...prevMap, [keyCode]: userId })); } - const value: AudioContextInterface = { - selectedTeammate, - setSelectedTeamMember, - addTeamShortcutBinding, - isRecording, - teamShortcutMappings, - isMuted: false, - isSilenceMode: false, - hasRecPermit, - }; + function selectTeamMember(userId: string) { + setSelectedTeamMember(userId); + } console.log(value);