Add mic/webcam switching

This commit is contained in:
Robert Long 2021-10-12 16:52:20 -07:00
parent 2e7dfe85e6
commit 5a714cef8d
3 changed files with 127 additions and 44 deletions

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from "react";
import styles from "./Room.module.css"; import styles from "./Room.module.css";
import { useLocation, useParams } from "react-router-dom"; import { useLocation, useParams } from "react-router-dom";
import { import {
@ -23,6 +23,7 @@ import {
VideoButton, VideoButton,
LayoutToggleButton, LayoutToggleButton,
ScreenshareButton, ScreenshareButton,
DropdownButton,
} from "./RoomButton"; } from "./RoomButton";
import { Header, LeftNav, RightNav, CenterNav } from "./Header"; import { Header, LeftNav, RightNav, CenterNav } from "./Header";
import { Button } from "./Input"; import { Button } from "./Input";
@ -83,7 +84,7 @@ export function Room({ client }) {
); );
} }
export function GroupCallView({ groupCall }) { export function GroupCallView({ client, groupCall }) {
const { const {
state, state,
error, error,
@ -108,6 +109,7 @@ export function GroupCallView({ groupCall }) {
} else if (state === GroupCallState.Entered) { } else if (state === GroupCallState.Entered) {
return ( return (
<InRoomView <InRoomView
client={client}
roomName={groupCall.room.name} roomName={groupCall.room.name}
microphoneMuted={microphoneMuted} microphoneMuted={microphoneMuted}
localVideoMuted={localVideoMuted} localVideoMuted={localVideoMuted}
@ -224,9 +226,70 @@ function RoomSetupView({
); );
} }
function useMediaManager() {} function useMediaHandler(client) {
const [{ audioInput, videoInput, audioInputs, videoInputs }, setState] =
useState({
audioInput: null,
videoInput: null,
audioInputs: [],
videoInputs: [],
});
useEffect(() => {
function updateDevices() {
navigator.mediaDevices.enumerateDevices().then((devices) => {
const audioInputs = devices.filter(
(device) => device.kind === "audioinput"
);
const videoInputs = devices.filter(
(device) => device.kind === "videoinput"
);
setState((prevState) => ({
...prevState,
audioInputs,
videoInputs,
}));
});
}
updateDevices();
navigator.mediaDevices.addEventListener("devicechange", updateDevices);
return () => {
navigator.mediaDevices.removeEventListener("devicechange", updateDevices);
};
}, []);
const setAudioInput = useCallback(
(deviceId) => {
setState((prevState) => ({ ...prevState, audioInput: deviceId }));
client.getMediaHandler().setAudioInput(deviceId);
},
[client]
);
const setVideoInput = useCallback(
(deviceId) => {
setState((prevState) => ({ ...prevState, videoInput: deviceId }));
client.getMediaHandler().setVideoInput(deviceId);
},
[client]
);
return {
audioInput,
audioInputs,
setAudioInput,
videoInput,
videoInputs,
setVideoInput,
};
}
function InRoomView({ function InRoomView({
client,
roomName, roomName,
microphoneMuted, microphoneMuted,
localVideoMuted, localVideoMuted,
@ -242,13 +305,13 @@ function InRoomView({
const [layout, toggleLayout] = useVideoGridLayout(); const [layout, toggleLayout] = useVideoGridLayout();
const { const {
audioDeviceId, audioInput,
audioDevices, audioInputs,
setAudioDevice, setAudioInput,
videoDeviceId, videoInput,
videoDevices, videoInputs,
setVideoDevice, setVideoInput,
} = useMediaManager(); } = useMediaHandler(client);
const items = useMemo(() => { const items = useMemo(() => {
const participants = []; const participants = [];
@ -295,26 +358,29 @@ function InRoomView({
<VideoGrid items={items} layout={layout} /> <VideoGrid items={items} layout={layout} />
)} )}
<div className={styles.footer}> <div className={styles.footer}>
<MicButton <DropdownButton
muted={microphoneMuted} value={audioInput}
onClick={toggleMicrophoneMuted} onChange={({ value }) => setAudioInput(value)}
value={audioDeviceId} options={audioInputs.map(({ label, deviceId }) => ({
onChange={({ value }) => setAudioDevice(value)}
options={audioDevices.map(({ label, deviceId }) => ({
label, label,
value: deviceId, value: deviceId,
}))} }))}
/> >
<VideoButton <MicButton muted={microphoneMuted} onClick={toggleMicrophoneMuted} />
enabled={localVideoMuted} </DropdownButton>
onClick={toggleLocalVideoMuted} <DropdownButton
value={videoDeviceId} value={videoInput}
onChange={({ value }) => setVideoDevice(value)} onChange={({ value }) => setVideoInput(value)}
options={videoDevices.map(({ label, deviceId }) => ({ options={videoInputs.map(({ label, deviceId }) => ({
label, label,
value: deviceId, value: deviceId,
}))} }))}
/> >
<VideoButton
enabled={localVideoMuted}
onClick={toggleLocalVideoMuted}
/>
</DropdownButton>
<ScreenshareButton <ScreenshareButton
enabled={isScreensharing} enabled={isScreensharing}
onClick={toggleScreensharing} onClick={toggleScreensharing}

View file

@ -1,4 +1,4 @@
import React from "react"; import React, { useRef, useState, useEffect } from "react";
import classNames from "classnames"; import classNames from "classnames";
import styles from "./RoomButton.module.css"; import styles from "./RoomButton.module.css";
import { ReactComponent as MicIcon } from "./icons/Mic.svg"; import { ReactComponent as MicIcon } from "./icons/Mic.svg";
@ -11,7 +11,6 @@ import { ReactComponent as GridIcon } from "./icons/Grid.svg";
import { ReactComponent as SpeakerIcon } from "./icons/Speaker.svg"; import { ReactComponent as SpeakerIcon } from "./icons/Speaker.svg";
import { ReactComponent as ScreenshareIcon } from "./icons/Screenshare.svg"; import { ReactComponent as ScreenshareIcon } from "./icons/Screenshare.svg";
import { ReactComponent as ChevronIcon } from "./icons/Chevron.svg"; import { ReactComponent as ChevronIcon } from "./icons/Chevron.svg";
import { useEffect } from "react";
export function RoomButton({ on, className, children, ...rest }) { export function RoomButton({ on, className, children, ...rest }) {
return ( return (
@ -24,7 +23,7 @@ export function RoomButton({ on, className, children, ...rest }) {
); );
} }
export function DropdownButton({ onChange, options, children }) { export function DropdownButton({ onChange, options, value, children }) {
const buttonRef = useRef(); const buttonRef = useRef();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@ -43,7 +42,7 @@ export function DropdownButton({ onChange, options, children }) {
}, [open]); }, [open]);
return ( return (
<div className={styles.dropDownButtonContainer}> <div className={styles.dropdownButtonContainer}>
{children} {children}
<button <button
ref={buttonRef} ref={buttonRef}
@ -53,13 +52,13 @@ export function DropdownButton({ onChange, options, children }) {
<ChevronIcon /> <ChevronIcon />
</button> </button>
{open && ( {open && (
<div className={styles.dropDownContainer}> <div className={styles.dropdownContainer}>
<ul> <ul>
{options.map((item) => ( {options.map((item) => (
<li <li
key={item.value} key={item.value}
className={classNames({ className={classNames({
[styles.dropDownActiveItem]: item.value === value, [styles.dropdownActiveItem]: item.value === value,
})} })}
onClick={() => onChange(item)} onClick={() => onChange(item)}
> >
@ -73,23 +72,19 @@ export function DropdownButton({ onChange, options, children }) {
); );
} }
export function MicButton({ muted, onChange, value, options, ...rest }) { export function MicButton({ muted, ...rest }) {
return ( return (
<DropdownButton onChange={onChange} options={options} value={value}> <RoomButton {...rest} on={muted}>
<RoomButton {...rest} on={muted}> {muted ? <MuteMicIcon /> : <MicIcon />}
{muted ? <MuteMicIcon /> : <MicIcon />} </RoomButton>
</RoomButton>
</DropdownButton>
); );
} }
export function VideoButton({ enabled, onChange, value, ...rest }) { export function VideoButton({ enabled, ...rest }) {
return ( return (
<DropdownButton onChange={onChange} options={options} value={value}> <RoomButton {...rest} on={enabled}>
<RoomButton {...rest} on={enabled}> {enabled ? <DisableVideoIcon /> : <VideoIcon />}
{enabled ? <DisableVideoIcon /> : <VideoIcon />} </RoomButton>
</RoomButton>
</DropdownButton>
); );
} }

View file

@ -87,6 +87,7 @@ limitations under the License.
position: absolute; position: absolute;
bottom: 0; bottom: 0;
right: 0; right: 0;
cursor: pointer;
} }
.dropdownButton:hover { .dropdownButton:hover {
@ -100,8 +101,29 @@ limitations under the License.
.dropdownContainer { .dropdownContainer {
position: absolute; position: absolute;
left: 50%; left: 50%;
transform: translate(-50%, -100%); transform: translate(0, -100%);
top: 0; top: -5px;
background-color: #394049; background-color: #394049;
border-radius: 8px; border-radius: 8px;
overflow: hidden;
}
.dropdownContainer ul {
list-style: none;
margin: 0;
padding: 0;
}
.dropdownContainer li {
padding: 12px;
width: 200px;
cursor: pointer;
}
.dropdownContainer li:hover {
background-color: #8d97a5;
}
.dropdownActiveItem {
color: #0dbd8b;
} }