-
- {/*
*/}
-
-
+
+ {tracks.length > 0 ? (
+
+ {(trackRef: TrackReferenceOrPlaceholder) => (
+
+
+
+ )}
+
+ ) : (
+
+
No participants with video yet
-
- >
+ )}
+
+
+
+
+
);
-}
+}
\ No newline at end of file
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..f7c9e59
--- /dev/null
+++ b/styles/CustomControlBar.css
@@ -0,0 +1,180 @@
+@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);
+ -webkit-backdrop-filter: blur(5px); /* Added for Safari compatibility */
+}
+
+.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;
+}
+
+/* Mic button */
+.mic-button[data-lk-audio-enabled="false"] {
+ background: rgba(255, 82, 82, 0.2);
+}
+
+.mic-button[data-lk-audio-enabled="true"] .material-symbols-outlined {
+ color: #ffffff;
+}
+
+.mic-button[data-lk-audio-enabled="false"] .material-symbols-outlined {
+ color: #ff6f6f;
+}
+
+/* Camera btn */
+.camera-button[data-lk-video-enabled="false"] {
+ background: rgba(255, 82, 82, 0.2);
+}
+
+.camera-button[data-lk-video-enabled="true"] .material-symbols-outlined {
+ color: #ffffff;
+}
+
+.camera-button[data-lk-video-enabled="false"] .material-symbols-outlined {
+ color: #ff6f6f;
+}
+
+/* Screen share btn */
+.screen-share-button[data-lk-screen-share-enabled="true"] .material-symbols-outlined {
+ color: #49c998;
+}
+
+.screen-share-button[data-lk-screen-share-enabled="false"] .material-symbols-outlined {
+ color: #ffffff;
+}
+
+/* Record */
+.record-sign .material-symbols-outlined {
+ color: #ff6f6f;
+ animation: pulse 1.5s infinite;
+}
+
+.record-sign.disabled .material-symbols-outlined {
+ color: #ffffff;
+ animation: none;
+}
+
+@keyframes pulse {
+ 0% { opacity: 1; }
+ 50% { opacity: 0.5; }
+ 100% { opacity: 1; }
+}
+
+/* End call */
+.end-call-button {
+ width: 64px;
+ height: 40px;
+ background: #ff5252;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.end-call-button .material-symbols-outlined {
+ color: #ffffff;
+}
+
+
+.top-right-controls {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+}
+
+.participant-box {
+ background-color: rgba(144, 155, 170, 0.1);
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ padding: 5px 10px;
+ gap: 8px;
+}
+
+.participant-box .material-symbols-outlined {
+ font-size: 20px;
+ color: white;
+}
+
+.participant-count {
+ font-size: 16px;
+ font-weight: 500;
+ color: white;
+ font-family: 'Roboto', sans-serif;
+}
+
+.settings-box {
+ background-color: rgba(144, 155, 170, 0.1);
+ border-radius: 8px;
+ width: 40px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+}
+
+.settings-box .material-symbols-outlined {
+ font-size: 20px;
+ color: white;
+}
+
+/* Fix for video layout */
+.lk-grid-layout {
+ height: calc(100vh - 60px) !important;
+}
\ No newline at end of file
diff --git a/styles/PageClientImpl.css b/styles/PageClientImpl.css
new file mode 100644
index 0000000..67a47ed
--- /dev/null
+++ b/styles/PageClientImpl.css
@@ -0,0 +1,25 @@
+.main-container {
+ height: 100%;
+ }
+
+ .pre-join-container {
+ display: grid;
+ place-items: center;
+ height: 100%;
+ }
+
+ .video-grid {
+ height: calc(100vh - 60px) !important;
+ }
+
+ .video-container {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ }
+
+ .empty-video-container {
+ height: calc(100vh - 60px);
+ display: grid;
+ place-items: center;
+ }
\ No newline at end of file
diff --git a/styles/VideoTrack.css b/styles/VideoTrack.css
new file mode 100644
index 0000000..be08892
--- /dev/null
+++ b/styles/VideoTrack.css
@@ -0,0 +1,5 @@
+.video-element {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
\ No newline at end of file