Add mic/webcam switching
This commit is contained in:
parent
2e7dfe85e6
commit
5a714cef8d
3 changed files with 127 additions and 44 deletions
114
src/Room.jsx
114
src/Room.jsx
|
@ -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}
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue