Skip to content

Commit 535772f

Browse files
committed
feat: system/tab audio on supported systems
1 parent 12a3e5c commit 535772f

File tree

2 files changed

+120
-48
lines changed

2 files changed

+120
-48
lines changed

ui/src/Room.tsx

Lines changed: 105 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import React, {useCallback} from 'react';
2-
import {Badge, IconButton, Paper, Tooltip, Typography} from '@mui/material';
2+
import {Badge, Box, IconButton, Paper, Tooltip, Typography, Slider, Stack} from '@mui/material';
33
import CancelPresentationIcon from '@mui/icons-material/CancelPresentation';
44
import PresentToAllIcon from '@mui/icons-material/PresentToAll';
55
import FullScreenIcon from '@mui/icons-material/Fullscreen';
66
import PeopleIcon from '@mui/icons-material/People';
7+
import VolumeMuteIcon from '@mui/icons-material/VolumeOff';
8+
import VolumeIcon from '@mui/icons-material/VolumeUp';
79
import SettingsIcon from '@mui/icons-material/Settings';
810
import {useHotkeys} from 'react-hotkeys-hook';
911
import {Video} from './Video';
@@ -97,7 +99,17 @@ export const Room = ({
9799
React.useEffect(() => {
98100
if (videoElement && stream) {
99101
videoElement.srcObject = stream;
100-
videoElement.play().catch((e) => console.log('Could not play main video', e));
102+
videoElement.play().catch((err) => {
103+
console.log('Could not play main video', err);
104+
if (err.name === 'NotAllowedError') {
105+
videoElement.muted = true;
106+
videoElement
107+
.play()
108+
.catch((retryErr) =>
109+
console.log('Could not play main video with mute', retryErr)
110+
);
111+
}
112+
});
101113
}
102114
}, [videoElement, stream]);
103115

@@ -161,6 +173,15 @@ export const Room = ({
161173
},
162174
[state.clientStreams, selectedStream]
163175
);
176+
useHotkeys(
177+
'm',
178+
() => {
179+
if (videoElement) {
180+
videoElement.muted = !videoElement.muted;
181+
}
182+
},
183+
[videoElement]
184+
);
164185

165186
const videoClasses = () => {
166187
switch (settings.displayMode) {
@@ -194,7 +215,6 @@ export const Room = ({
194215

195216
{stream ? (
196217
<video
197-
muted
198218
ref={setVideoElement}
199219
className={videoClasses()}
200220
onDoubleClick={handleFullscreen}
@@ -217,53 +237,58 @@ export const Room = ({
217237

218238
{controlVisible && (
219239
<Paper className={classes.control} elevation={10} {...setHoverState}>
220-
{state.hostStream ? (
221-
<Tooltip title="Cancel Presentation" arrow>
222-
<IconButton onClick={stopShare} size="large">
223-
<CancelPresentationIcon fontSize="large" />
224-
</IconButton>
225-
</Tooltip>
226-
) : (
227-
<Tooltip title="Start Presentation" arrow>
228-
<IconButton onClick={share} size="large">
229-
<PresentToAllIcon fontSize="large" />
230-
</IconButton>
231-
</Tooltip>
240+
{(stream?.getAudioTracks().length ?? 0) > 0 && videoElement && (
241+
<AudioControl video={videoElement} />
232242
)}
243+
<Box whiteSpace="nowrap">
244+
{state.hostStream ? (
245+
<Tooltip title="Cancel Presentation" arrow>
246+
<IconButton onClick={stopShare} size="large">
247+
<CancelPresentationIcon fontSize="large" />
248+
</IconButton>
249+
</Tooltip>
250+
) : (
251+
<Tooltip title="Start Presentation" arrow>
252+
<IconButton onClick={share} size="large">
253+
<PresentToAllIcon fontSize="large" />
254+
</IconButton>
255+
</Tooltip>
256+
)}
233257

234-
<Tooltip
235-
classes={{tooltip: classes.noMaxWidth}}
236-
title={
237-
<div>
238-
<Typography variant="h5">Member List</Typography>
239-
{state.users.map((user) => (
240-
<Typography key={user.id}>
241-
{user.name} {flags(user)}
242-
</Typography>
243-
))}
244-
</div>
245-
}
246-
arrow
247-
>
248-
<Badge badgeContent={state.users.length} color="primary">
249-
<PeopleIcon fontSize="large" />
250-
</Badge>
251-
</Tooltip>
252-
<Tooltip title="Fullscreen" arrow>
253-
<IconButton
254-
onClick={() => handleFullscreen()}
255-
disabled={!selectedStream}
256-
size="large"
258+
<Tooltip
259+
classes={{tooltip: classes.noMaxWidth}}
260+
title={
261+
<div>
262+
<Typography variant="h5">Member List</Typography>
263+
{state.users.map((user) => (
264+
<Typography key={user.id}>
265+
{user.name} {flags(user)}
266+
</Typography>
267+
))}
268+
</div>
269+
}
270+
arrow
257271
>
258-
<FullScreenIcon fontSize="large" />
259-
</IconButton>
260-
</Tooltip>
272+
<Badge badgeContent={state.users.length} color="primary">
273+
<PeopleIcon fontSize="large" />
274+
</Badge>
275+
</Tooltip>
276+
<Tooltip title="Fullscreen" arrow>
277+
<IconButton
278+
onClick={() => handleFullscreen()}
279+
disabled={!selectedStream}
280+
size="large"
281+
>
282+
<FullScreenIcon fontSize="large" />
283+
</IconButton>
284+
</Tooltip>
261285

262-
<Tooltip title="Settings" arrow>
263-
<IconButton onClick={() => setOpen(true)} size="large">
264-
<SettingsIcon fontSize="large" />
265-
</IconButton>
266-
</Tooltip>
286+
<Tooltip title="Settings" arrow>
287+
<IconButton onClick={() => setOpen(true)} size="large">
288+
<SettingsIcon fontSize="large" />
289+
</IconButton>
290+
</Tooltip>
291+
</Box>
267292
</Paper>
268293
)}
269294

@@ -353,6 +378,40 @@ const useShowOnMouseMovement = (doShow: (s: boolean) => void) => {
353378
);
354379
};
355380

381+
const AudioControl = ({video}: {video: FullScreenHTMLVideoElement}) => {
382+
// this is used to force a rerender
383+
const [, setMuted] = React.useState<boolean>();
384+
385+
React.useEffect(() => {
386+
const handler = () => setMuted(video.muted);
387+
video.addEventListener('volumechange', handler);
388+
setMuted(video.muted);
389+
return () => video.removeEventListener('volumechange', handler);
390+
});
391+
392+
return (
393+
<Stack spacing={0.5} pr={1} direction="row" sx={{alignItems: 'center', my: 1, height: 35}}>
394+
<IconButton size="large" onClick={() => (video.muted = !video.muted)}>
395+
{video.muted ? (
396+
<VolumeMuteIcon fontSize="large" />
397+
) : (
398+
<VolumeIcon fontSize="large" />
399+
)}
400+
</IconButton>
401+
<Slider
402+
min={0}
403+
max={1}
404+
step={0.01}
405+
defaultValue={video.volume}
406+
onChange={(e, newVolume) => {
407+
video.muted = false;
408+
video.volume = newVolume;
409+
}}
410+
/>
411+
</Stack>
412+
);
413+
};
414+
356415
const useStyles = makeStyles(() => ({
357416
title: {
358417
padding: 15,

ui/src/useRoom.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,15 @@ const clientSession = async ({
143143
done();
144144
}
145145
};
146+
147+
let notified = false;
148+
const stream = new MediaStream();
146149
peer.ontrack = (event) => {
147-
const stream = new MediaStream();
148150
stream.addTrack(event.track);
149-
onTrack(stream);
151+
if (!notified) {
152+
notified = true;
153+
onTrack(stream);
154+
}
150155
};
151156

152157
return peer;
@@ -332,6 +337,14 @@ export const useRoom = (config: UIConfig): UseRoom => {
332337
try {
333338
stream.current = await navigator.mediaDevices.getDisplayMedia({
334339
video: {frameRate: loadSettings().framerate},
340+
audio: {
341+
echoCancellation: false,
342+
autoGainControl: false,
343+
noiseSuppression: false,
344+
// https://medium.com/@trystonperry/why-is-getdisplaymedias-audio-quality-so-bad-b49ba9cfaa83
345+
// @ts-expect-error
346+
googAutoGainControl: false,
347+
},
335348
});
336349
} catch (e) {
337350
console.log('Could not getDisplayMedia', e);

0 commit comments

Comments
 (0)