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:
parent
ccf818d34b
commit
1b01e60424
8 changed files with 205 additions and 16 deletions
|
@ -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
|
||||
|
|
|
@ -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
141
lib/SettingsMenu.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
18
pnpm-lock.yaml
generated
|
@ -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
|
||||
|
||||
|
|
23
styles/SettingsMenu.module.css
Normal file
23
styles/SettingsMenu.module.css
Normal 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);
|
||||
}
|
Loading…
Add table
Reference in a new issue