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.
 | 
					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";
 | 
				
			||||||
| 
						 | 
					@ -78,12 +79,12 @@ export function Room({ client }) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className={styles.room}>
 | 
					    <div className={styles.room}>
 | 
				
			||||||
      <GroupCallView groupCall={groupCall} />
 | 
					      <GroupCallView client={client} groupCall={groupCall} />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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,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({
 | 
					function InRoomView({
 | 
				
			||||||
 | 
					  client,
 | 
				
			||||||
  roomName,
 | 
					  roomName,
 | 
				
			||||||
  microphoneMuted,
 | 
					  microphoneMuted,
 | 
				
			||||||
  localVideoMuted,
 | 
					  localVideoMuted,
 | 
				
			||||||
| 
						 | 
					@ -239,6 +304,15 @@ function InRoomView({
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
  const [layout, toggleLayout] = useVideoGridLayout();
 | 
					  const [layout, toggleLayout] = useVideoGridLayout();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    audioInput,
 | 
				
			||||||
 | 
					    audioInputs,
 | 
				
			||||||
 | 
					    setAudioInput,
 | 
				
			||||||
 | 
					    videoInput,
 | 
				
			||||||
 | 
					    videoInputs,
 | 
				
			||||||
 | 
					    setVideoInput,
 | 
				
			||||||
 | 
					  } = useMediaHandler(client);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const items = useMemo(() => {
 | 
					  const items = useMemo(() => {
 | 
				
			||||||
    const participants = [];
 | 
					    const participants = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -284,11 +358,29 @@ function InRoomView({
 | 
				
			||||||
        <VideoGrid items={items} layout={layout} />
 | 
					        <VideoGrid items={items} layout={layout} />
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
      <div className={styles.footer}>
 | 
					      <div className={styles.footer}>
 | 
				
			||||||
 | 
					        <DropdownButton
 | 
				
			||||||
 | 
					          value={audioInput}
 | 
				
			||||||
 | 
					          onChange={({ value }) => setAudioInput(value)}
 | 
				
			||||||
 | 
					          options={audioInputs.map(({ label, deviceId }) => ({
 | 
				
			||||||
 | 
					            label,
 | 
				
			||||||
 | 
					            value: deviceId,
 | 
				
			||||||
 | 
					          }))}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
          <MicButton muted={microphoneMuted} onClick={toggleMicrophoneMuted} />
 | 
					          <MicButton muted={microphoneMuted} onClick={toggleMicrophoneMuted} />
 | 
				
			||||||
 | 
					        </DropdownButton>
 | 
				
			||||||
 | 
					        <DropdownButton
 | 
				
			||||||
 | 
					          value={videoInput}
 | 
				
			||||||
 | 
					          onChange={({ value }) => setVideoInput(value)}
 | 
				
			||||||
 | 
					          options={videoInputs.map(({ label, deviceId }) => ({
 | 
				
			||||||
 | 
					            label,
 | 
				
			||||||
 | 
					            value: deviceId,
 | 
				
			||||||
 | 
					          }))}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
          <VideoButton
 | 
					          <VideoButton
 | 
				
			||||||
            enabled={localVideoMuted}
 | 
					            enabled={localVideoMuted}
 | 
				
			||||||
            onClick={toggleLocalVideoMuted}
 | 
					            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";
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@ import { ReactComponent as SettingsIcon } from "./icons/Settings.svg";
 | 
				
			||||||
import { ReactComponent as GridIcon } from "./icons/Grid.svg";
 | 
					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";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RoomButton({ on, className, children, ...rest }) {
 | 
					export function RoomButton({ on, className, children, ...rest }) {
 | 
				
			||||||
  return (
 | 
					  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 }) {
 | 
					export function MicButton({ muted, ...rest }) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <RoomButton {...rest} on={muted}>
 | 
					    <RoomButton {...rest} on={muted}>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,8 @@ limitations under the License.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.roomButton,
 | 
					.roomButton,
 | 
				
			||||||
.headerButton {
 | 
					.headerButton,
 | 
				
			||||||
 | 
					.dropdownButton {
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  justify-content: center;
 | 
					  justify-content: center;
 | 
				
			||||||
  align-items: center;
 | 
					  align-items: center;
 | 
				
			||||||
| 
						 | 
					@ -29,7 +30,7 @@ limitations under the License.
 | 
				
			||||||
  width: 50px;
 | 
					  width: 50px;
 | 
				
			||||||
  height: 50px;
 | 
					  height: 50px;
 | 
				
			||||||
  border-radius: 50px;
 | 
					  border-radius: 50px;
 | 
				
			||||||
  background-color: rgba(111, 120, 130, 0.3);
 | 
					  background-color: #394049;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.roomButton:hover {
 | 
					.roomButton:hover {
 | 
				
			||||||
| 
						 | 
					@ -73,3 +74,56 @@ limitations under the License.
 | 
				
			||||||
.screenshareButton.on svg * {
 | 
					.screenshareButton.on svg * {
 | 
				
			||||||
  fill: #0dbd8b;
 | 
					  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…
	
	Add table
		Add a link
		
	
		Reference in a new issue