From 9f6c4d724987f2f5a1d1ce5ab1af4bfc6afe626d Mon Sep 17 00:00:00 2001
From: anujsb <141896390+anujsb@users.noreply.github.com>
Date: Sun, 23 Feb 2025 17:32:31 +0530
Subject: [PATCH] Fix : LiveKit call redesign - Bottom bar
---
.env.example | 30 ---
app/custom/CustomControlBar.tsx | 123 +++++++++++++
app/rooms/[roomName]/PageClientImpl.tsx | 232 +++++++++++++++---------
package.json | 1 +
pnpm-lock.yaml | 105 +++++++----
styles/CustomControlBar.css | 186 +++++++++++++++++++
6 files changed, 526 insertions(+), 151 deletions(-)
delete mode 100644 .env.example
create mode 100644 app/custom/CustomControlBar.tsx
create mode 100644 styles/CustomControlBar.css
diff --git a/.env.example b/.env.example
deleted file mode 100644
index f961fa0..0000000
--- a/.env.example
+++ /dev/null
@@ -1,30 +0,0 @@
-# 1. Copy this file and rename it to .env.local
-# 2. Update the enviroment variables below.
-
-# REQUIRED SETTINGS
-# #################
-# If you are using LiveKit Cloud, the API key and secret can be generated from the Cloud Dashboard.
-LIVEKIT_API_KEY=
-LIVEKIT_API_SECRET=
-# URL pointing to the LiveKit server. (example: `wss://my-livekit-project.livekit.cloud`)
-LIVEKIT_URL=
-
-
-# OPTIONAL SETTINGS
-# #################
-# Recording
-# S3_KEY_ID=
-# S3_KEY_SECRET=
-# S3_ENDPOINT=
-# S3_BUCKET=
-# S3_REGION=
-
-# PUBLIC
-# Uncomment settings menu when using a LiveKit Cloud, it'll enable Krisp noise filters.
-# NEXT_PUBLIC_SHOW_SETTINGS_MENU=true
-# NEXT_PUBLIC_LK_RECORD_ENDPOINT=/api/record
-
-# Optional, to pipe logs to datadog
-# NEXT_PUBLIC_DATADOG_CLIENT_TOKEN=client-token
-# NEXT_PUBLIC_DATADOG_SITE=datadog-site
-
diff --git a/app/custom/CustomControlBar.tsx b/app/custom/CustomControlBar.tsx
new file mode 100644
index 0000000..4269dba
--- /dev/null
+++ b/app/custom/CustomControlBar.tsx
@@ -0,0 +1,123 @@
+// // import React from 'react';
+// // import { TrackToggle, DisconnectButton } from '@livekit/components-react';
+// // import SettingsMenu from '@/lib/SettingsMenu';
+// // import { MaterialSymbol } from 'material-symbols';
+// // import '../../styles/CustomControlBar.css';
+
+
+// // const CustomControlBar = ({ roomName, room }) => {
+// // const [recording, setRecording] = React.useState(false);
+// // const [showSettings, setShowSettings] = React.useState(false);
+
+// // React.useEffect(() => {
+// // if (room) {
+// // room.on('recordedStatusChanged', () => {
+// // setRecording(room.isRecording);
+// // });
+// // return () => {
+// // room.off('recordedStatusChanged');
+// // };
+// // }
+// // }, [room]);
+
+// // const handleCopyLink = () => {
+// // navigator.clipboard.writeText(window.location.href);
+// // };
+
+// // return (
+// //
+// //
+// //
+// // {roomName}
+// //
+// //
+// //
+// //
+// //
+// //
+// // {recording && (
+// //
+// // {/* */}
+// //
+// // )}
+// //
+// //
+// //
+// //
+// //
+// // {showSettings && setShowSettings(false)} />}
+// //
+// //
+// // );
+// // };
+
+// // export default CustomControlBar;
+
+
+// import { TrackToggle, DisconnectButton, RoomAudioRenderer, GridLayout } from '@livekit/components-react';
+// import { useState, useEffect } from 'react';
+
+// function CustomControlBar({ room, roomName }) {
+// const [recording, setRecording] = useState(false);
+
+// // Update recording status
+// useEffect(() => {
+// if (room) {
+// const updateRecordingStatus = () => setRecording(room.isRecording);
+// room.on(RoomEvent.RecordingStarted, updateRecordingStatus);
+// room.on(RoomEvent.RecordingStopped, updateRecordingStatus);
+// return () => {
+// room.off(RoomEvent.RecordingStarted, updateRecordingStatus);
+// room.off(RoomEvent.RecordingStopped, updateRecordingStatus);
+// };
+// }
+// }, [room]);
+
+// // Copy room link to clipboard
+// const handleCopyLink = () => {
+// navigator.clipboard.writeText(window.location.href)
+// .then(() => alert('Link copied to clipboard!'))
+// .catch((err) => console.error('Failed to copy link:', err));
+// };
+
+// return (
+//
+// {/* Left: Room Name Box */}
+//
+// {roomName}
+//
+//
+
+// {/* Center: Control Buttons */}
+//
+//
+//
+// {recording && (
+//
+// radio_button_checked
+//
+// )}
+//
+//
+// call_end
+//
+//
+
+// {/* Right: Settings Button */}
+//
+// {SHOW_SETTINGS_MENU && (
+//
+// )}
+//
+//
+// );
+// }
\ No newline at end of file
diff --git a/app/rooms/[roomName]/PageClientImpl.tsx b/app/rooms/[roomName]/PageClientImpl.tsx
index daf17f9..063d7a0 100644
--- a/app/rooms/[roomName]/PageClientImpl.tsx
+++ b/app/rooms/[roomName]/PageClientImpl.tsx
@@ -1,36 +1,38 @@
+
'use client';
import { decodePassphrase } from '@/lib/client-utils';
import Transcript from '@/lib/Transcript';
-import { RecordingIndicator } from '@/lib/RecordingIndicator';
import { SettingsMenu } from '@/lib/SettingsMenu';
import { ConnectionDetails } from '@/lib/types';
import {
- formatChatMessageLinks,
- LiveKitRoom,
LocalUserChoices,
PreJoin,
- VideoConference,
+ LiveKitRoom,
+ TrackToggle,
+ DisconnectButton,
+ RoomAudioRenderer,
+ GridLayout,
+ useTracks,
+ TrackReferenceOrPlaceholder,
} from '@livekit/components-react';
import {
ExternalE2EEKeyProvider,
RoomOptions,
RoomEvent,
- TranscriptionSegment,
VideoCodec,
VideoPresets,
Room,
DeviceUnsupportedError,
RoomConnectOptions,
+ Track,
} from 'livekit-client';
-import { setLazyProp } from 'next/dist/server/api-utils';
import { useRouter } from 'next/navigation';
-import React from 'react';
-
+import React, { useEffect, useRef, useState } from 'react';
+import '../../../styles/CustomControlBar.css';
const CONN_DETAILS_ENDPOINT =
process.env.NEXT_PUBLIC_CONN_DETAILS_ENDPOINT ?? '/api/connection-details';
const SHOW_SETTINGS_MENU = process.env.NEXT_PUBLIC_SHOW_SETTINGS_MENU == 'true';
-console.log('SHOW_SETTINGS_MENU', SHOW_SETTINGS_MENU);
export function PageClientImpl(props: {
roomName: string;
@@ -38,9 +40,7 @@ export function PageClientImpl(props: {
hq: boolean;
codec: VideoCodec;
}) {
- const [preJoinChoices, setPreJoinChoices] = React.useState(
- undefined,
- );
+ const [preJoinChoices, setPreJoinChoices] = React.useState(undefined);
const preJoinDefaults = React.useMemo(() => {
return {
username: '',
@@ -48,9 +48,7 @@ export function PageClientImpl(props: {
audioEnabled: true,
};
}, []);
- const [connectionDetails, setConnectionDetails] = React.useState(
- undefined,
- );
+ const [connectionDetails, setConnectionDetails] = React.useState(undefined);
const handlePreJoinSubmit = React.useCallback(async (values: LocalUserChoices) => {
setPreJoinChoices(values);
@@ -63,7 +61,8 @@ export function PageClientImpl(props: {
const connectionDetailsResp = await fetch(url.toString());
const connectionDetailsData = await connectionDetailsResp.json();
setConnectionDetails(connectionDetailsData);
- }, []);
+ }, [props.roomName, props.region]);
+
const handlePreJoinError = React.useCallback((e: any) => console.error(e), []);
return (
@@ -90,14 +89,10 @@ export function PageClientImpl(props: {
function VideoConferenceComponent(props: {
userChoices: LocalUserChoices;
connectionDetails: ConnectionDetails;
- options: {
- hq: boolean;
- codec: VideoCodec;
- };
+ options: { hq: boolean; codec: VideoCodec };
}) {
const e2eePassphrase =
typeof window !== 'undefined' && decodePassphrase(location.hash.substring(1));
-
const worker =
typeof window !== 'undefined' &&
e2eePassphrase &&
@@ -129,17 +124,12 @@ function VideoConferenceComponent(props: {
adaptiveStream: { pixelDensity: 'screen' },
dynacast: true,
e2ee: e2eeEnabled
- ? {
- keyProvider,
- worker,
- }
+ ? { keyProvider, worker }
: undefined,
};
- // @ts-ignore
- setLogLevel('debug', 'lk-e2ee');
}, [props.userChoices, props.options.hq, props.options.codec]);
- const room = React.useMemo(() => new Room(roomOptions), []);
+ const room = React.useMemo(() => new Room(roomOptions), [roomOptions]);
if (e2eeEnabled) {
keyProvider.setKey(decodePassphrase(e2eePassphrase));
@@ -152,65 +142,143 @@ function VideoConferenceComponent(props: {
}
});
}
- const connectOptions = React.useMemo((): RoomConnectOptions => {
- return {
- autoSubscribe: true,
- };
- }, []);
- const [transcriptions, setTranscriptions] = React.useState<{
- [id: string]: TranscriptionSegment;
- }>({});
- const [latestText, setLatestText] = React.useState('');
- React.useEffect(() => {
- if (!room) {
- return;
- }
- const updateTranscriptions = (
- segments: TranscriptionSegment[],
- participant: any,
- publication: any,
- ) => {
- if (segments.length > 0) {
- setLatestText(segments[0].text);
- }
- // setTranscriptions((prev) => {
- // const newTranscriptions = { ...prev };
- // for (const segment of segments) {
- // newTranscriptions[segment.id] = segment;
- // }
- // console.log('===>', newTranscriptions);
- // return newTranscriptions;
- // });
- };
- room.on(RoomEvent.TranscriptionReceived, updateTranscriptions);
- return () => {
- room.off(RoomEvent.TranscriptionReceived, updateTranscriptions);
- };
- }, [room]);
+ const connectOptions = React.useMemo((): RoomConnectOptions => {
+ return { autoSubscribe: true };
+ }, []);
const router = useRouter();
const handleOnLeave = React.useCallback(() => router.push('/'), [router]);
+ const tracks = useTracks(
+ [{ source: Track.Source.Camera, withPlaceholder: true }],
+ { room }
+ );
+
return (
- <>
-
-
- {/* */}
-
-
-
- >
+
+ {tracks.length > 0 ? (
+
+ {(trackRef: TrackReferenceOrPlaceholder) => (
+
+
+
+ )}
+
+ ) : (
+
+
No participants with video yet
+
+ )}
+
+
+
+
);
}
+
+function VideoTrack({ ref: trackRef }: { ref: TrackReferenceOrPlaceholder }) {
+ const videoRef = useRef(null);
+
+ useEffect(() => {
+ const videoEl = videoRef.current;
+ const track = trackRef.publication?.track;
+
+ if (videoEl && track) {
+ track.attach(videoEl);
+ return () => {
+ track.detach(videoEl);
+ };
+ }
+ }, [trackRef.publication?.track]);
+
+ return (
+
+ );
+}
+
+interface CustomControlBarProps {
+ room: Room;
+ roomName: string;
+}
+interface CustomControlBarProps {
+ room: Room;
+ roomName: string;
+}
+
+function CustomControlBar({ room, roomName }: CustomControlBarProps) {
+ const [recording, setRecording] = useState(false);
+
+ useEffect(() => {
+ if (room) {
+ const updateRecordingStatus = () => setRecording(room.isRecording);
+ room.on(RoomEvent.LocalTrackPublished, updateRecordingStatus);
+ room.on(RoomEvent.LocalTrackUnpublished, updateRecordingStatus);
+ return () => {
+ room.off(RoomEvent.LocalTrackPublished, updateRecordingStatus);
+ room.off(RoomEvent.LocalTrackUnpublished, updateRecordingStatus);
+ };
+ }
+ }, [room]);
+
+ const handleCopyLink = () => {
+ navigator.clipboard.writeText(window.location.href)
+ .then(() => alert('Link copied to clipboard!'))
+ .catch((err) => console.error('Failed to copy link:', err));
+ };
+
+ return (
+
+ {/* Left: Room Name Box */}
+
+ {roomName}
+
+
+
+ {/* Center: Control Buttons */}
+
+
+
+ {recording ? (
+
+ radio_button_checked
+
+ ) : (
+
+ radio_button_checked
+
+ )}
+
+
+ call_end
+
+
+
+ {/* Right: Settings Button */}
+
+ {SHOW_SETTINGS_MENU && (
+
+ )}
+
+
+ );
+}
+
diff --git a/package.json b/package.json
index 13413d7..aaf098e 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"@livekit/krisp-noise-filter": "^0.2.8",
"livekit-client": "2.8.1",
"livekit-server-sdk": "2.9.7",
+ "material-symbols": "^0.28.2",
"next": "14.2.12",
"react": "18.3.1",
"react-dom": "18.3.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7b2137e..f0f8262 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -13,19 +13,22 @@ importers:
version: 5.26.0
'@livekit/components-react':
specifier: 2.6.0
- version: 2.6.0(@livekit/protocol@1.20.1)(livekit-client@2.5.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tslib@2.7.0)
+ version: 2.6.0(@livekit/protocol@1.34.0)(livekit-client@2.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tslib@2.8.1)
'@livekit/components-styles':
specifier: 1.1.2
version: 1.1.2
'@livekit/krisp-noise-filter':
specifier: ^0.2.8
- version: 0.2.8(livekit-client@2.5.2)
+ version: 0.2.8(livekit-client@2.8.1)
livekit-client:
- specifier: 2.5.2
- version: 2.5.2
+ specifier: 2.8.1
+ version: 2.8.1
livekit-server-sdk:
- specifier: 2.6.2
- version: 2.6.2
+ specifier: 2.9.7
+ version: 2.9.7
+ material-symbols:
+ specifier: ^0.28.2
+ version: 0.28.2
next:
specifier: 14.2.12
version: 14.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -43,8 +46,8 @@ importers:
specifier: 20.16.3
version: 20.16.3
'@types/react':
- specifier: 18.3.5
- version: 18.3.5
+ specifier: 18.3.8
+ version: 18.3.8
'@types/react-dom':
specifier: 18.3.0
version: 18.3.0
@@ -172,8 +175,14 @@ packages:
peerDependencies:
livekit-client: ^2.0.8
- '@livekit/protocol@1.20.1':
- resolution: {integrity: sha512-TgyuwOx+XJn9inEYT9OKfFNs9YIPS4BdLa4pF5FDf9MhWRnahKwPe7jxr/+sVdWxYbZmy9hRrH58jSAFu0ONHw==}
+ '@livekit/mutex@1.1.1':
+ resolution: {integrity: sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw==}
+
+ '@livekit/protocol@1.30.0':
+ resolution: {integrity: sha512-SDI9ShVKj8N3oOSinr8inaxD3FXgmgoJlqN35uU/Yx1sdoDeQbzAuBFox7bYjM+VhnZ1V22ivIDjAsKr00H+XQ==}
+
+ '@livekit/protocol@1.34.0':
+ resolution: {integrity: sha512-bU7pCLAMRVTVZb1KSxA46q55bhOc4iATrY/gccy2/oX1D57tiZEI+8wGRWHeDwBb0UwnABu6JXzC4tTFkdsaOg==}
'@next/env@14.2.12':
resolution: {integrity: sha512-3fP29GIetdwVIfIRyLKM7KrvJaqepv+6pVodEbx0P5CaMLYBtx+7eEg8JYO5L9sveJO87z9eCReceZLi0hxO1Q==}
@@ -282,8 +291,8 @@ packages:
'@types/react-dom@18.3.0':
resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==}
- '@types/react@18.3.5':
- resolution: {integrity: sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==}
+ '@types/react@18.3.8':
+ resolution: {integrity: sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q==}
'@typescript-eslint/parser@7.2.0':
resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==}
@@ -1101,12 +1110,12 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
- livekit-client@2.5.2:
- resolution: {integrity: sha512-rzWFH02UznHxpnbj+WEEoHxL1ZSo9BdFK+7ltSZWniTt2llnNckdqeXNsjkBH6k+C9agHTF4XikmxKcpWa4YrQ==}
+ livekit-client@2.8.1:
+ resolution: {integrity: sha512-HPv9iHNrnBANI9ucK7CKZspx0sBZK3hjR2EbwaV08+J3RM9+tNGL2ob2n76nxJLEZG7LzdWlLZdbr4fQBP6Hkg==}
- livekit-server-sdk@2.6.2:
- resolution: {integrity: sha512-3fFzHu7sAynUaUFTCKtRP9lgQCU0Qe/x7XA99GpT1ro7fTy1ZVzaWq34WcXEyUGBBMFxG19LlSIAQBcGZVStWQ==}
- engines: {node: '>=19'}
+ livekit-server-sdk@2.9.7:
+ resolution: {integrity: sha512-uIkFOaqBCJnVgYOidZdanPWQH5G0LMxe0+Qp5zbx7MZCkJ7lGiju//yonfEvFofriJBKACjMq/KQHBex96QpeA==}
+ engines: {node: '>=18'}
loader-runner@4.3.0:
resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
@@ -1137,6 +1146,9 @@ packages:
resolution: {integrity: sha512-2L3MIgJynYrZ3TYMriLDLWocz15okFakV6J12HXvMXDHui2x/zgChzg1u9mFFGbbGWE+GsLpQByt4POb9Or+uA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ material-symbols@0.28.2:
+ resolution: {integrity: sha512-JLK+Bgtfg5Dn9V2WYk6lSwmxciNNF2zmqc/V8MLmH0K9LttUhPCaauJzrS1Vw3mJPs/Tyfi/tszynNRX6nWQOA==}
+
merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
@@ -1577,6 +1589,9 @@ packages:
tslib@2.7.0:
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@@ -1777,33 +1792,39 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
- '@livekit/components-core@0.11.5(@livekit/protocol@1.20.1)(livekit-client@2.5.2)(tslib@2.7.0)':
+ '@livekit/components-core@0.11.5(@livekit/protocol@1.34.0)(livekit-client@2.8.1)(tslib@2.8.1)':
dependencies:
'@floating-ui/dom': 1.6.11
- '@livekit/protocol': 1.20.1
- livekit-client: 2.5.2
+ '@livekit/protocol': 1.34.0
+ livekit-client: 2.8.1
loglevel: 1.9.1
rxjs: 7.8.1
- tslib: 2.7.0
+ tslib: 2.8.1
- '@livekit/components-react@2.6.0(@livekit/protocol@1.20.1)(livekit-client@2.5.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tslib@2.7.0)':
+ '@livekit/components-react@2.6.0(@livekit/protocol@1.34.0)(livekit-client@2.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tslib@2.8.1)':
dependencies:
- '@livekit/components-core': 0.11.5(@livekit/protocol@1.20.1)(livekit-client@2.5.2)(tslib@2.7.0)
- '@livekit/protocol': 1.20.1
+ '@livekit/components-core': 0.11.5(@livekit/protocol@1.34.0)(livekit-client@2.8.1)(tslib@2.8.1)
+ '@livekit/protocol': 1.34.0
clsx: 2.1.1
- livekit-client: 2.5.2
+ livekit-client: 2.8.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
- tslib: 2.7.0
+ tslib: 2.8.1
usehooks-ts: 3.1.0(react@18.3.1)
'@livekit/components-styles@1.1.2': {}
- '@livekit/krisp-noise-filter@0.2.8(livekit-client@2.5.2)':
+ '@livekit/krisp-noise-filter@0.2.8(livekit-client@2.8.1)':
dependencies:
- livekit-client: 2.5.2
+ livekit-client: 2.8.1
- '@livekit/protocol@1.20.1':
+ '@livekit/mutex@1.1.1': {}
+
+ '@livekit/protocol@1.30.0':
+ dependencies:
+ '@bufbuild/protobuf': 1.10.0
+
+ '@livekit/protocol@1.34.0':
dependencies:
'@bufbuild/protobuf': 1.10.0
@@ -1880,9 +1901,9 @@ snapshots:
'@types/react-dom@18.3.0':
dependencies:
- '@types/react': 18.3.5
+ '@types/react': 18.3.8
- '@types/react@18.3.5':
+ '@types/react@18.3.8':
dependencies:
'@types/prop-types': 15.7.12
csstype: 3.1.3
@@ -2403,7 +2424,7 @@ snapshots:
eslint: 9.9.1
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@9.9.1)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@9.9.1))(eslint@9.9.1)
- eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@9.9.1)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.9.1)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@9.9.1)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@9.9.1)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@9.9.1))(eslint@9.9.1))(eslint@9.9.1)
eslint-plugin-jsx-a11y: 6.9.0(eslint@9.9.1)
eslint-plugin-react: 7.35.0(eslint@9.9.1)
eslint-plugin-react-hooks: 4.6.2(eslint@9.9.1)
@@ -2434,7 +2455,7 @@ snapshots:
is-bun-module: 1.1.0
is-glob: 4.0.3
optionalDependencies:
- eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@9.9.1)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.9.1)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@9.9.1)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@9.9.1)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@9.9.1))(eslint@9.9.1))(eslint@9.9.1)
transitivePeerDependencies:
- '@typescript-eslint/parser'
- eslint-import-resolver-node
@@ -2452,7 +2473,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@9.9.1)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.9.1):
+ eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@9.9.1)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@9.9.1)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@9.9.1))(eslint@9.9.1))(eslint@9.9.1):
dependencies:
array-includes: 3.1.8
array.prototype.findlastindex: 1.2.5
@@ -2924,20 +2945,22 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
- livekit-client@2.5.2:
+ livekit-client@2.8.1:
dependencies:
- '@livekit/protocol': 1.20.1
+ '@livekit/mutex': 1.1.1
+ '@livekit/protocol': 1.30.0
events: 3.3.0
loglevel: 1.9.1
sdp-transform: 2.14.2
ts-debounce: 4.0.0
- tslib: 2.7.0
+ tslib: 2.8.1
typed-emitter: 2.1.0
webrtc-adapter: 9.0.1
- livekit-server-sdk@2.6.2:
+ livekit-server-sdk@2.9.7:
dependencies:
- '@livekit/protocol': 1.20.1
+ '@bufbuild/protobuf': 1.10.0
+ '@livekit/protocol': 1.34.0
camelcase-keys: 9.1.3
jose: 5.8.0
@@ -2961,6 +2984,8 @@ snapshots:
map-obj@5.0.0: {}
+ material-symbols@0.28.2: {}
+
merge-stream@2.0.0: {}
merge2@1.4.1: {}
@@ -3408,6 +3433,8 @@ snapshots:
tslib@2.7.0: {}
+ tslib@2.8.1: {}
+
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
diff --git a/styles/CustomControlBar.css b/styles/CustomControlBar.css
new file mode 100644
index 0000000..bc719a9
--- /dev/null
+++ b/styles/CustomControlBar.css
@@ -0,0 +1,186 @@
+@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
+@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
+
+.custom-control-bar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px;
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: rgba(0, 0, 0, 0.8);
+ z-index: 1000;
+ backdrop-filter: blur(5px);
+}
+
+.room-name-box {
+ background: rgba(144, 155, 170, 0.1);
+ border-radius: 8px;
+ padding: 5px 10px;
+ display: flex;
+ align-items: center;
+ gap: 5px;
+}
+
+.room-name {
+ font-family: 'Roboto', sans-serif;
+ font-size: 14px;
+ color: #909baa;
+}
+
+.copy-link-button {
+ background: none;
+ border: none;
+ cursor: pointer;
+ margin-left: 5px;
+ padding: 0;
+}
+
+.copy-link-button .material-symbols-outlined {
+ font-size: 20px;
+ color: #909baa;
+}
+
+.control-buttons {
+ display: flex;
+ gap: 10px;
+ justify-content: center;
+ flex-grow: 1;
+}
+
+.control-button {
+ width: 40px;
+ height: 40px;
+ background: rgba(144, 155, 170, 0.1);
+ border-radius: 8px;
+ border: none;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 24px;
+}
+
+.mic-button[data-lk-audio-enabled="false"] {
+ background: #ff5252;
+ opacity: 0.8;
+}
+
+.mic-button[data-lk-audio-enabled="true"] .material-symbols-outlined {
+ content: 'mic';
+ color: #ffffff;
+}
+
+.mic-button[data-lk-audio-enabled="false"] .material-symbols-outlined {
+ content: 'mic_off';
+ color: #ffffff;
+}
+
+.camera-button[data-lk-video-enabled="false"] {
+ background: #ff5252;
+ opacity: 0.8;
+}
+
+.camera-button[data-lk-video-enabled="true"] .material-symbols-outlined {
+ content: 'videocam';
+ color: #ffffff;
+}
+
+.camera-button[data-lk-video-enabled="false"] .material-symbols-outlined {
+ content: 'videocam_off';
+ color: #ffffff;
+}
+
+.screen-share-button[data-lk-screen-share-enabled="true"] .material-symbols-outlined {
+ content: 'screen_share';
+ color: #49c998;
+}
+
+.screen-share-button[data-lk-screen-share-enabled="false"] .material-symbols-outlined {
+ content: 'screen_share';
+ color: #ffffff;
+}
+
+.record-sign {
+ background: rgba(144, 155, 170, 0.1);
+}
+
+.record-sign.disabled {
+ background: rgba(144, 155, 170, 0.1);
+}
+
+.record-sign .material-symbols-outlined,
+.record-sign.disabled .material-symbols-outlined {
+ font-size: 24px;
+}
+
+.record-sign .material-symbols-outlined {
+ color: #ff6f6f;
+ animation: pulse 1s infinite;
+}
+
+.record-sign.disabled .material-symbols-outlined {
+ color: #ffffff;
+}
+
+@keyframes pulse {
+ 0% { opacity: 1; }
+ 50% { opacity: 0.5; }
+ 100% { opacity: 1; }
+}
+
+.end-call-button {
+ width: 64px;
+ height: 40px;
+ background: #ff5252;
+ border-radius: 8px;
+ border: none;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.end-call-button .material-symbols-outlined {
+ font-size: 24px;
+ color: #ffffff;
+}
+
+.settings-section {
+ position: relative;
+}
+
+.settings-button {
+ width: 40px;
+ height: 40px;
+ background: rgba(144, 155, 170, 0.1);
+ border-radius: 8px;
+ border: none;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.settings-button .material-symbols-outlined {
+ font-size: 24px;
+ color: #ffffff;
+}
+
+
+.lk-grid-layout {
+ height: calc(100vh - 60px) !important;
+ width: 100% !important;
+}
+
+.lk-grid-layout > div {
+ position: relative !important;
+ overflow: hidden !important;
+}
+
+.lk-video {
+ object-fit: cover !important;
+ background-color: #000 !important;
+}
\ No newline at end of file