Skip to content

Commit

Permalink
Add audio output setting when available
Browse files Browse the repository at this point in the history
  • Loading branch information
robertlong committed Feb 23, 2022
1 parent 81a763f commit 2c3ebd4
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 92 deletions.
4 changes: 4 additions & 0 deletions src/room/InCallView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { UserMenuContainer } from "../UserMenuContainer";
import { useRageshakeRequestModal } from "../settings/rageshake";
import { RageshakeRequestModal } from "./RageshakeRequestModal";
import { usePreventScroll } from "@react-aria/overlays";
import { useMediaHandler } from "../settings/useMediaHandler";

const canScreenshare = "getDisplayMedia" in navigator.mediaDevices;
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
Expand Down Expand Up @@ -51,6 +52,8 @@ export function InCallView({
usePreventScroll();
const [layout, setLayout] = useVideoGridLayout(screenshareFeeds.length > 0);

const { audioOutput } = useMediaHandler();

const items = useMemo(() => {
const participants = [];

Expand Down Expand Up @@ -159,6 +162,7 @@ export function InCallView({
item={item}
getAvatar={renderAvatar}
showName={items.length > 2 || item.focused}
audioOutputDevice={audioOutput}
{...rest}
/>
)}
Expand Down
4 changes: 3 additions & 1 deletion src/room/LobbyView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useProfile } from "../profile/useProfile";
import useMeasure from "react-use-measure";
import { ResizeObserver } from "@juggle/resize-observer";
import { useLocationNavigation } from "../useLocationNavigation";
import { useMediaHandler } from "../settings/useMediaHandler";

export function LobbyView({
client,
Expand All @@ -31,7 +32,8 @@ export function LobbyView({
roomId,
}) {
const { stream } = useCallFeed(localCallFeed);
const videoRef = useMediaStream(stream, true);
const { audioOutput } = useMediaHandler();
const videoRef = useMediaStream(stream, audioOutput, true);
const { displayName, avatarUrl } = useProfile(client);
const [previewRef, previewBounds] = useMeasure({ polyfill: ResizeObserver });
const avatarSize = (previewBounds.height - 66) / 2;
Expand Down
1 change: 0 additions & 1 deletion src/room/OverflowMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ export function OverflowMenu({
{...settingsModalProps}
setShowInspector={setShowInspector}
showInspector={showInspector}
client={client}
/>
)}
{inviteModalState.isOpen && (
Expand Down
25 changes: 14 additions & 11 deletions src/room/RoomPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ErrorView, LoadingView } from "../FullScreenView";
import { RoomAuthView } from "./RoomAuthView";
import { GroupCallLoader } from "./GroupCallLoader";
import { GroupCallView } from "./GroupCallView";
import { MediaHandlerProvider } from "../settings/useMediaHandler";

export function RoomPage() {
const { loading, isAuthenticated, error, client, isPasswordlessUser } =
Expand All @@ -47,16 +48,18 @@ export function RoomPage() {
}

return (
<GroupCallLoader client={client} roomId={roomId} viaServers={viaServers}>
{(groupCall) => (
<GroupCallView
client={client}
roomId={roomId}
groupCall={groupCall}
isPasswordlessUser={isPasswordlessUser}
simpleGrid={simpleGrid}
/>
)}
</GroupCallLoader>
<MediaHandlerProvider client={client}>
<GroupCallLoader client={client} roomId={roomId} viaServers={viaServers}>
{(groupCall) => (
<GroupCallView
client={client}
roomId={roomId}
groupCall={groupCall}
isPasswordlessUser={isPasswordlessUser}
simpleGrid={simpleGrid}
/>
)}
</GroupCallLoader>
</MediaHandlerProvider>
);
}
23 changes: 16 additions & 7 deletions src/settings/SettingsModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,18 @@ import { Button } from "../button";
import { useDownloadDebugLog } from "./rageshake";
import { Body } from "../typography/Typography";

export function SettingsModal({
client,
setShowInspector,
showInspector,
...rest
}) {
export function SettingsModal({ setShowInspector, showInspector, ...rest }) {
const {
audioInput,
audioInputs,
setAudioInput,
videoInput,
videoInputs,
setVideoInput,
} = useMediaHandler(client);
audioOutput,
audioOutputs,
setAudioOutput,
} = useMediaHandler();

const downloadDebugLog = useDownloadDebugLog();

Expand Down Expand Up @@ -56,6 +54,17 @@ export function SettingsModal({
<Item key={deviceId}>{label}</Item>
))}
</SelectInput>
{audioOutputs.length > 0 && (
<SelectInput
label="Speaker"
selectedKey={audioOutput}
onSelectionChange={setAudioOutput}
>
{audioOutputs.map(({ deviceId, label }) => (
<Item key={deviceId}>{label}</Item>
))}
</SelectInput>
)}
</TabItem>
<TabItem
title={
Expand Down
72 changes: 0 additions & 72 deletions src/settings/useMediaHandler.js

This file was deleted.

143 changes: 143 additions & 0 deletions src/settings/useMediaHandler.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React, {
useState,
useEffect,
useCallback,
useMemo,
useContext,
createContext,
} from "react";

const MediaHandlerContext = createContext();

export function MediaHandlerProvider({ client, children }) {
const [
{
audioInput,
videoInput,
audioInputs,
videoInputs,
audioOutput,
audioOutputs,
},
setState,
] = useState(() => {
const mediaHandler = client.getMediaHandler();

return {
audioInput: mediaHandler.audioInput,
videoInput: mediaHandler.videoInput,
audioOutput: undefined,
audioInputs: [],
videoInputs: [],
audioOutputs: [],
};
});

useEffect(() => {
const mediaHandler = client.getMediaHandler();

function updateDevices() {
navigator.mediaDevices.enumerateDevices().then((devices) => {
const audioInputs = devices.filter(
(device) => device.kind === "audioinput"
);
const videoInputs = devices.filter(
(device) => device.kind === "videoinput"
);
const audioOutputs = devices.filter(
(device) => device.kind === "audiooutput"
);

let audioOutput = undefined;

const audioOutputPreference = localStorage.getItem(
"matrix-audio-output"
);

if (
audioOutputPreference &&
audioOutputs.some(
(device) => device.deviceId === audioOutputPreference
)
) {
audioOutput = audioOutputPreference;
}

setState({
audioInput: mediaHandler.audioInput,
videoInput: mediaHandler.videoInput,
audioOutput,
audioInputs,
audioOutputs,
videoInputs,
});
});
}

updateDevices();

mediaHandler.on("local_streams_changed", updateDevices);
navigator.mediaDevices.addEventListener("devicechange", updateDevices);

return () => {
mediaHandler.removeListener("local_streams_changed", updateDevices);
navigator.mediaDevices.removeEventListener("devicechange", updateDevices);
};
}, [client]);

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]
);

const setAudioOutput = useCallback((deviceId) => {
localStorage.setItem("matrix-audio-output", deviceId);
setState((prevState) => ({ ...prevState, audioOutput: deviceId }));
}, []);

const context = useMemo(
() => ({
audioInput,
audioInputs,
setAudioInput,
videoInput,
videoInputs,
setVideoInput,
audioOutput,
audioOutputs,
setAudioOutput,
}),
[
audioInput,
audioInputs,
setAudioInput,
videoInput,
videoInputs,
setVideoInput,
audioOutput,
audioOutputs,
setAudioOutput,
]
);

return (
<MediaHandlerContext.Provider value={context}>
{children}
</MediaHandlerContext.Provider>
);
}

export function useMediaHandler() {
return useContext(MediaHandlerContext);
}

0 comments on commit 2c3ebd4

Please sign in to comment.