Add settings menu (#209)

* Add settings menu

* update livekit deps

* Update components

* Handle unsupported browsers

* make settings model configurable via env

* update env

* better check against env
This commit is contained in:
lukasIO 2024-03-05 15:48:46 +01:00 committed by GitHub
parent ccf818d34b
commit 1b01e60424
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 205 additions and 16 deletions

View file

@ -9,4 +9,6 @@ LIVEKIT_API_SECRET=secret
LIVEKIT_URL=wss://my-livekit-project.livekit.cloud
## PUBLIC
NEXT_PUBLIC_LK_TOKEN_ENDPOINT=/api/token
NEXT_PUBLIC_LK_TOKEN_ENDPOINT=/api/token
NEXT_PUBLIC_SHOW_SETTINGS_MENU=true

View file

@ -1,6 +1,6 @@
import * as React from 'react';
import { useRoomContext } from '@livekit/components-react';
import { setLogLevel, LogLevel, RemoteTrackPublication } from 'livekit-client';
import { setLogLevel, getLogger, LogLevel, RemoteTrackPublication } from 'livekit-client';
import { tinykeys } from 'tinykeys';
import styles from '../styles/Debug.module.css';
@ -9,6 +9,9 @@ export const useDebugMode = ({ logLevel }: { logLevel?: LogLevel }) => {
React.useEffect(() => {
setLogLevel(logLevel ?? 'debug');
// @ts-ignore
setLogLevel('debug', 'lk-e2ee');
// @ts-expect-error
window.__lk_room = room;

141
lib/SettingsMenu.tsx Normal file
View file

@ -0,0 +1,141 @@
'use client';
import * as React from 'react';
import { LocalAudioTrack, Track } from 'livekit-client';
import {
useMaybeLayoutContext,
useLocalParticipant,
MediaDeviceMenu,
TrackToggle,
} from '@livekit/components-react';
import styles from '../styles/SettingsMenu.module.css';
/**
* @alpha
*/
export interface SettingsMenuProps extends React.HTMLAttributes<HTMLDivElement> {}
/**
* @alpha
*/
export function SettingsMenu(props: SettingsMenuProps) {
const layoutContext = useMaybeLayoutContext();
const settings = React.useMemo(() => {
return {
media: { camera: true, microphone: true, label: 'Media Devices', speaker: false },
effects: { label: 'Effects' },
};
}, []);
const tabs = React.useMemo(
() => Object.keys(settings) as Array<keyof typeof settings>,
[settings],
);
const { microphoneTrack } = useLocalParticipant();
const [activeTab, setActiveTab] = React.useState(tabs[0]);
const [isNoiseFilterEnabled, setIsNoiseFilterEnabled] = React.useState(true);
React.useEffect(() => {
const micPublication = microphoneTrack;
if (micPublication && micPublication.track instanceof LocalAudioTrack) {
const currentProcessor = micPublication.track.getProcessor();
if (currentProcessor && !isNoiseFilterEnabled) {
micPublication.track.stopProcessor();
} else if (!currentProcessor && isNoiseFilterEnabled) {
import('@livekit/noise-filter')
.then(({ NoiseFilter, isNoiseFilterSupported }) => {
if (!isNoiseFilterSupported()) {
console.error('Enhanced noise filter is not supported for this browser');
setIsNoiseFilterEnabled(false);
return;
}
micPublication?.track
// @ts-ignore
?.setProcessor(NoiseFilter())
.then(() => console.log('successfully set noise filter'));
})
.catch((e) => console.error('Failed to load noise filter', e));
}
}
}, [isNoiseFilterEnabled, microphoneTrack]);
return (
<div className="settings-menu" style={{ width: '100%' }} {...props}>
<div className={styles.tabs}>
{tabs.map(
(tab) =>
settings[tab] && (
<button
className={`${styles.tab} lk-button`}
key={tab}
onClick={() => setActiveTab(tab)}
aria-pressed={tab === activeTab}
>
{
// @ts-ignore
settings[tab].label
}
</button>
),
)}
</div>
<div className="tab-content">
{activeTab === 'media' && (
<>
{settings.media && settings.media.camera && (
<>
<h3>Camera</h3>
<section className="lk-button-group">
<TrackToggle source={Track.Source.Camera}>Camera</TrackToggle>
<div className="lk-button-group-menu">
<MediaDeviceMenu kind="videoinput" />
</div>
</section>
</>
)}
{settings.media && settings.media.microphone && (
<>
<h3>Microphone</h3>
<section className="lk-button-group">
<TrackToggle source={Track.Source.Microphone}>Camera</TrackToggle>
<div className="lk-button-group-menu">
<MediaDeviceMenu kind="audioinput" />
</div>
</section>
</>
)}
{settings.media && settings.media.speaker && (
<>
<h3>Speaker & Headphones</h3>
<section>
<MediaDeviceMenu kind="audiooutput"></MediaDeviceMenu>
</section>
</>
)}
</>
)}
{activeTab === 'effects' && (
<>
<h3>Audio</h3>
<section>
<label htmlFor="noise-filter"> Enhanced Noise Cancellation</label>
<input
type="checkbox"
id="noise-filter"
onChange={(ev) => setIsNoiseFilterEnabled(ev.target.checked)}
checked={isNoiseFilterEnabled}
></input>
</section>
</>
)}
</div>
<button
className={`lk-button ${styles.settingsCloseButton}`}
onClick={() => layoutContext?.widget.dispatch?.({ msg: 'toggle_settings' })}
>
Close
</button>
</div>
);
}

View file

@ -5,14 +5,11 @@ const nextConfig = {
productionBrowserSourceMaps: true,
webpack: (config, { buildId, dev, isServer, defaultLoaders, nextRuntime, webpack }) => {
// Important: return the modified config
config.module.rules = [
...config.module.rules,
{
test: /\.mjs$/,
enforce: 'pre',
use: ['source-map-loader'],
},
];
config.module.rules.push({
test: /\.mjs$/,
enforce: 'pre',
use: ['source-map-loader'],
});
return config;
},
};

View file

@ -11,6 +11,7 @@
"dependencies": {
"@livekit/components-react": "2.0.2",
"@livekit/components-styles": "1.0.10",
"@livekit/noise-filter": "^0.1.3",
"livekit-client": "2.0.4",
"livekit-server-sdk": "2.0.4",
"next": "14.1.0",
@ -30,5 +31,8 @@
},
"engines": {
"node": ">=18"
},
"pnpm": {
"overrides": {}
}
}

View file

@ -15,6 +15,7 @@ import {
RoomOptions,
VideoCodec,
VideoPresets,
setLogLevel,
} from 'livekit-client';
import type { NextPage } from 'next';
@ -24,6 +25,7 @@ import { useRouter } from 'next/router';
import * as React from 'react';
import { DebugMode } from '../../lib/Debug';
import { decodePassphrase, useServerUrl } from '../../lib/client-utils';
import { SettingsMenu } from '../../lib/SettingsMenu';
const PreJoinNoSSR = dynamic(
async () => {
@ -144,6 +146,8 @@ const ActiveRoom = ({ roomName, userChoices, onLeave }: ActiveRoomProps) => {
}
: undefined,
};
// @ts-ignore
setLogLevel('debug', 'lk-e2ee');
}, [userChoices, hq, codec]);
const room = React.useMemo(() => new Room(roomOptions), []);
@ -177,8 +181,13 @@ const ActiveRoom = ({ roomName, userChoices, onLeave }: ActiveRoomProps) => {
audio={userChoices.audioEnabled}
onDisconnected={onLeave}
>
<VideoConference chatMessageFormatter={formatChatMessageLinks} />
<DebugMode logLevel={LogLevel.debug} />
<VideoConference
chatMessageFormatter={formatChatMessageLinks}
SettingsComponent={
process.env.NEXT_PUBLIC_SHOW_SETTINGS_MENU === 'true' ? SettingsMenu : undefined
}
/>
<DebugMode />
</LiveKitRoom>
)}
</>

18
pnpm-lock.yaml generated
View file

@ -11,6 +11,9 @@ dependencies:
'@livekit/components-styles':
specifier: 1.0.10
version: 1.0.10
'@livekit/noise-filter':
specifier: ^0.1.3
version: 0.1.3(livekit-client@2.0.4)
livekit-client:
specifier: 2.0.4
version: 2.0.4
@ -236,6 +239,14 @@ packages:
engines: {node: '>=18'}
dev: false
/@livekit/noise-filter@0.1.3(livekit-client@2.0.4):
resolution: {integrity: sha512-y5FVS8wOmDJ40ml3nqVJ2I40pFUwIx37B3XiLhPQrgXaY2saZpZebfN4D22XsbaEqPs5kCMQ82IqdLxRkQpbMA==}
peerDependencies:
livekit-client: ^2.0.2
dependencies:
livekit-client: 2.0.4
dev: false
/@next/env@14.1.0:
resolution: {integrity: sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw==}
dev: false
@ -1971,7 +1982,7 @@ packages:
'@bufbuild/protobuf': 1.4.2
events: 3.3.0
loglevel: 1.9.1
sdp-transform: 2.14.1
sdp-transform: 2.14.2
ts-debounce: 4.0.0
tslib: 2.6.2
typed-emitter: 2.1.0
@ -2444,7 +2455,6 @@ packages:
/rxjs@7.8.1:
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
requiresBuild: true
dependencies:
tslib: 2.6.2
dev: false
@ -2490,8 +2500,8 @@ packages:
ajv-keywords: 3.5.2(ajv@6.12.6)
dev: true
/sdp-transform@2.14.1:
resolution: {integrity: sha512-RjZyX3nVwJyCuTo5tGPx+PZWkDMCg7oOLpSlhjDdZfwUoNqG1mM8nyj31IGHyaPWXhjbP7cdK3qZ2bmkJ1GzRw==}
/sdp-transform@2.14.2:
resolution: {integrity: sha512-icY6jVao7MfKCieyo1AyxFYm1baiM+fA00qW/KrNNVlkxHAd34riEKuEkUe4bBb3gJwLJZM+xT60Yj1QL8rHiA==}
hasBin: true
dev: false

View file

@ -0,0 +1,23 @@
.settingsCloseButton {
position: absolute;
right: var(--lk-grid-gap);
bottom: var(--lk-grid-gap);
}
.tabs {
position: relative;
display: flex;
align-content: space-between;
}
.tabs > .tab {
padding: 0.5rem;
border-radius: 0;
padding-bottom: 0.5rem;
border-bottom: 3px solid;
border-color: var(--bg5);
}
.tabs > .tab[aria-pressed='true'] {
border-color: var(--lk-accent-bg);
}