Merge pull request #52 from vector-im/change-audio-devices

Change audio devices
This commit is contained in:
Robert Long 2021-10-12 16:53:03 -07:00 committed by GitHub
commit 9047b1ae9b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 207 additions and 11 deletions

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
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 { useLocation, useParams } from "react-router-dom";
import {
@ -23,6 +23,7 @@ import {
VideoButton,
LayoutToggleButton,
ScreenshareButton,
DropdownButton,
} from "./RoomButton";
import { Header, LeftNav, RightNav, CenterNav } from "./Header";
import { Button } from "./Input";
@ -78,12 +79,12 @@ export function Room({ client }) {
return (
<div className={styles.room}>
<GroupCallView groupCall={groupCall} />
<GroupCallView client={client} groupCall={groupCall} />
</div>
);
}
export function GroupCallView({ groupCall }) {
export function GroupCallView({ client, groupCall }) {
const {
state,
error,
@ -108,6 +109,7 @@ export function GroupCallView({ groupCall }) {
} else if (state === GroupCallState.Entered) {
return (
<InRoomView
client={client}
roomName={groupCall.room.name}
microphoneMuted={microphoneMuted}
localVideoMuted={localVideoMuted}
@ -224,7 +226,70 @@ function RoomSetupView({
);
}
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({
client,
roomName,
microphoneMuted,
localVideoMuted,
@ -239,6 +304,15 @@ function InRoomView({
}) {
const [layout, toggleLayout] = useVideoGridLayout();
const {
audioInput,
audioInputs,
setAudioInput,
videoInput,
videoInputs,
setVideoInput,
} = useMediaHandler(client);
const items = useMemo(() => {
const participants = [];
@ -284,11 +358,29 @@ function InRoomView({
<VideoGrid items={items} layout={layout} />
)}
<div className={styles.footer}>
<DropdownButton
value={audioInput}
onChange={({ value }) => setAudioInput(value)}
options={audioInputs.map(({ label, deviceId }) => ({
label,
value: deviceId,
}))}
>
<MicButton muted={microphoneMuted} onClick={toggleMicrophoneMuted} />
</DropdownButton>
<DropdownButton
value={videoInput}
onChange={({ value }) => setVideoInput(value)}
options={videoInputs.map(({ label, deviceId }) => ({
label,
value: deviceId,
}))}
>
<VideoButton
enabled={localVideoMuted}
onClick={toggleLocalVideoMuted}
/>
</DropdownButton>
<ScreenshareButton
enabled={isScreensharing}
onClick={toggleScreensharing}

View file

@ -1,4 +1,4 @@
import React from "react";
import React, { useRef, useState, useEffect } from "react";
import classNames from "classnames";
import styles from "./RoomButton.module.css";
import { ReactComponent as MicIcon } from "./icons/Mic.svg";
@ -10,6 +10,7 @@ import { ReactComponent as SettingsIcon } from "./icons/Settings.svg";
import { ReactComponent as GridIcon } from "./icons/Grid.svg";
import { ReactComponent as SpeakerIcon } from "./icons/Speaker.svg";
import { ReactComponent as ScreenshareIcon } from "./icons/Screenshare.svg";
import { ReactComponent as ChevronIcon } from "./icons/Chevron.svg";
export function RoomButton({ on, className, children, ...rest }) {
return (
@ -22,6 +23,55 @@ export function RoomButton({ on, className, children, ...rest }) {
);
}
export function DropdownButton({ onChange, options, value, children }) {
const buttonRef = useRef();
const [open, setOpen] = useState(false);
useEffect(() => {
function onClick() {
if (open) {
setOpen(false);
}
}
window.addEventListener("click", onClick);
return () => {
window.removeEventListener("click", onClick);
};
}, [open]);
return (
<div className={styles.dropdownButtonContainer}>
{children}
<button
ref={buttonRef}
className={styles.dropdownButton}
onClick={() => setOpen(true)}
>
<ChevronIcon />
</button>
{open && (
<div className={styles.dropdownContainer}>
<ul>
{options.map((item) => (
<li
key={item.value}
className={classNames({
[styles.dropdownActiveItem]: item.value === value,
})}
onClick={() => onChange(item)}
>
{item.label}
</li>
))}
</ul>
</div>
)}
</div>
);
}
export function MicButton({ muted, ...rest }) {
return (
<RoomButton {...rest} on={muted}>

View file

@ -15,7 +15,8 @@ limitations under the License.
*/
.roomButton,
.headerButton {
.headerButton,
.dropdownButton {
display: flex;
justify-content: center;
align-items: center;
@ -29,7 +30,7 @@ limitations under the License.
width: 50px;
height: 50px;
border-radius: 50px;
background-color: rgba(111, 120, 130, 0.3);
background-color: #394049;
}
.roomButton:hover {
@ -73,3 +74,56 @@ limitations under the License.
.screenshareButton.on svg * {
fill: #0dbd8b;
}
.dropdownButtonContainer {
position: relative;
}
.dropdownButton {
width: 15px;
height: 15px;
border-radius: 15px;
background-color: #394049;
position: absolute;
bottom: 0;
right: 0;
cursor: pointer;
}
.dropdownButton:hover {
background-color: #8d97a5;
}
.dropdownButton:hover svg * {
fill: #8d97a5;
}
.dropdownContainer {
position: absolute;
left: 50%;
transform: translate(0, -100%);
top: -5px;
background-color: #394049;
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;
}