feat: system/tab audio on supported systems

This commit is contained in:
Jannis Mattheis
2025-05-07 23:44:20 +02:00
parent 12a3e5c3d4
commit 3f33538816
2 changed files with 120 additions and 48 deletions

View File

@@ -1,9 +1,11 @@
import React, {useCallback} from 'react'; import React, {useCallback} from 'react';
import {Badge, IconButton, Paper, Tooltip, Typography} from '@mui/material'; import {Badge, Box, IconButton, Paper, Tooltip, Typography, Slider, Stack} from '@mui/material';
import CancelPresentationIcon from '@mui/icons-material/CancelPresentation'; import CancelPresentationIcon from '@mui/icons-material/CancelPresentation';
import PresentToAllIcon from '@mui/icons-material/PresentToAll'; import PresentToAllIcon from '@mui/icons-material/PresentToAll';
import FullScreenIcon from '@mui/icons-material/Fullscreen'; import FullScreenIcon from '@mui/icons-material/Fullscreen';
import PeopleIcon from '@mui/icons-material/People'; import PeopleIcon from '@mui/icons-material/People';
import VolumeMuteIcon from '@mui/icons-material/VolumeOff';
import VolumeIcon from '@mui/icons-material/VolumeUp';
import SettingsIcon from '@mui/icons-material/Settings'; import SettingsIcon from '@mui/icons-material/Settings';
import {useHotkeys} from 'react-hotkeys-hook'; import {useHotkeys} from 'react-hotkeys-hook';
import {Video} from './Video'; import {Video} from './Video';
@@ -97,7 +99,17 @@ export const Room = ({
React.useEffect(() => { React.useEffect(() => {
if (videoElement && stream) { if (videoElement && stream) {
videoElement.srcObject = stream; videoElement.srcObject = stream;
videoElement.play().catch((e) => console.log('Could not play main video', e)); videoElement.play().catch((err) => {
console.log('Could not play main video', err);
if (err.name === 'NotAllowedError') {
videoElement.muted = true;
videoElement
.play()
.catch((retryErr) =>
console.log('Could not play main video with mute', retryErr)
);
}
});
} }
}, [videoElement, stream]); }, [videoElement, stream]);
@@ -161,6 +173,15 @@ export const Room = ({
}, },
[state.clientStreams, selectedStream] [state.clientStreams, selectedStream]
); );
useHotkeys(
'm',
() => {
if (videoElement) {
videoElement.muted = !videoElement.muted;
}
},
[videoElement]
);
const videoClasses = () => { const videoClasses = () => {
switch (settings.displayMode) { switch (settings.displayMode) {
@@ -194,7 +215,6 @@ export const Room = ({
{stream ? ( {stream ? (
<video <video
muted
ref={setVideoElement} ref={setVideoElement}
className={videoClasses()} className={videoClasses()}
onDoubleClick={handleFullscreen} onDoubleClick={handleFullscreen}
@@ -217,6 +237,10 @@ export const Room = ({
{controlVisible && ( {controlVisible && (
<Paper className={classes.control} elevation={10} {...setHoverState}> <Paper className={classes.control} elevation={10} {...setHoverState}>
{(stream?.getAudioTracks().length ?? 0) > 0 && videoElement && (
<AudioControl video={videoElement} />
)}
<Box whiteSpace="nowrap">
{state.hostStream ? ( {state.hostStream ? (
<Tooltip title="Cancel Presentation" arrow> <Tooltip title="Cancel Presentation" arrow>
<IconButton onClick={stopShare} size="large"> <IconButton onClick={stopShare} size="large">
@@ -264,6 +288,7 @@ export const Room = ({
<SettingsIcon fontSize="large" /> <SettingsIcon fontSize="large" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
</Box>
</Paper> </Paper>
)} )}
@@ -353,6 +378,40 @@ const useShowOnMouseMovement = (doShow: (s: boolean) => void) => {
); );
}; };
const AudioControl = ({video}: {video: FullScreenHTMLVideoElement}) => {
// this is used to force a rerender
const [, setMuted] = React.useState<boolean>();
React.useEffect(() => {
const handler = () => setMuted(video.muted);
video.addEventListener('volumechange', handler);
setMuted(video.muted);
return () => video.removeEventListener('volumechange', handler);
});
return (
<Stack spacing={0.5} pr={2} direction="row" sx={{alignItems: 'center', my: 1, height: 35}}>
<IconButton size="large" onClick={() => (video.muted = !video.muted)}>
{video.muted ? (
<VolumeMuteIcon fontSize="large" />
) : (
<VolumeIcon fontSize="large" />
)}
</IconButton>
<Slider
min={0}
max={1}
step={0.01}
defaultValue={video.volume}
onChange={(_, newVolume) => {
video.muted = false;
video.volume = newVolume;
}}
/>
</Stack>
);
};
const useStyles = makeStyles(() => ({ const useStyles = makeStyles(() => ({
title: { title: {
padding: 15, padding: 15,

View File

@@ -143,10 +143,15 @@ const clientSession = async ({
done(); done();
} }
}; };
peer.ontrack = (event) => {
let notified = false;
const stream = new MediaStream(); const stream = new MediaStream();
peer.ontrack = (event) => {
stream.addTrack(event.track); stream.addTrack(event.track);
if (!notified) {
notified = true;
onTrack(stream); onTrack(stream);
}
}; };
return peer; return peer;
@@ -332,6 +337,14 @@ export const useRoom = (config: UIConfig): UseRoom => {
try { try {
stream.current = await navigator.mediaDevices.getDisplayMedia({ stream.current = await navigator.mediaDevices.getDisplayMedia({
video: {frameRate: loadSettings().framerate}, video: {frameRate: loadSettings().framerate},
audio: {
echoCancellation: false,
autoGainControl: false,
noiseSuppression: false,
// https://medium.com/@trystonperry/why-is-getdisplaymedias-audio-quality-so-bad-b49ba9cfaa83
// @ts-expect-error
googAutoGainControl: false,
},
}); });
} catch (e) { } catch (e) {
console.log('Could not getDisplayMedia', e); console.log('Could not getDisplayMedia', e);