Merge pull request #52 from vector-im/change-audio-devices
Change audio devices
This commit is contained in:
commit
9047b1ae9b
3 changed files with 207 additions and 11 deletions
98
src/Room.jsx
98
src/Room.jsx
|
@ -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}
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue