livekit
This commit is contained in:
@@ -6,6 +6,8 @@ import { createAdapter } from "@socket.io/redis-adapter";
|
||||
import { jwtMiddleware } from "modules/socketJWTmiddleware";
|
||||
import { pubClient, subClient } from "modules/redis";
|
||||
import { handle } from "socket-events/connect-dispatch";
|
||||
import router from "routes/router";
|
||||
import cors from "cors";
|
||||
|
||||
const app = express();
|
||||
const server = createServer(app);
|
||||
@@ -14,12 +16,15 @@ const io = new Server(server, {
|
||||
adapter: createAdapter(pubClient, subClient),
|
||||
cors: {},
|
||||
});
|
||||
|
||||
io.use(jwtMiddleware);
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.on("connect-dispatch", handle(socket, io));
|
||||
});
|
||||
|
||||
app.use(cors());
|
||||
app.use(router);
|
||||
|
||||
server.listen(process.env.PORT, () => {
|
||||
console.log(`Server running on port ${process.env.PORT}`);
|
||||
});
|
||||
|
||||
@@ -5,4 +5,13 @@ export const subClient = pubClient.duplicate();
|
||||
|
||||
Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
|
||||
console.log("Redis connected");
|
||||
pubClient.keys("dispatchers*").then((keys) => {
|
||||
if (!keys) return;
|
||||
keys.forEach(async (key) => {
|
||||
await pubClient.json.del(key);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pubClient.on("error", (err) => console.log("Redis Client Error", err));
|
||||
subClient.on("error", (err) => console.log("Redis Client Error", err));
|
||||
|
||||
@@ -21,10 +21,12 @@
|
||||
"@redis/json": "^1.0.7",
|
||||
"@socket.io/redis-adapter": "^8.3.0",
|
||||
"axios": "^1.7.9",
|
||||
"cors": "^2.8.5",
|
||||
"cron": "^4.1.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"livekit-server-sdk": "^2.10.2",
|
||||
"nodemailer": "^6.10.0",
|
||||
"react": "^19.0.0",
|
||||
"redis": "^4.7.0",
|
||||
|
||||
38
apps/dispatch-server/routes/livekit.ts
Normal file
38
apps/dispatch-server/routes/livekit.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Router } from "express";
|
||||
import { AccessToken } from "livekit-server-sdk";
|
||||
|
||||
if (!process.env.LIVEKIT_API_KEY) throw new Error("LIVEKIT_API_KEY not set");
|
||||
if (!process.env.LIVEKIT_API_SECRET)
|
||||
throw new Error("LIVEKIT_API_SECRET not set");
|
||||
|
||||
const createToken = async () => {
|
||||
// If this room doesn't exist, it'll be automatically created when the first
|
||||
// participant joins
|
||||
const roomName = "quickstart-room";
|
||||
// Identifier to be used for participant.
|
||||
// It's available as LocalParticipant.identity with livekit-client SDK
|
||||
const participantName = "quickstart-username";
|
||||
|
||||
const at = new AccessToken(
|
||||
process.env.LIVEKIT_API_KEY,
|
||||
process.env.LIVEKIT_API_SECRET,
|
||||
{
|
||||
identity: participantName,
|
||||
// Token to expire after 10 minutes
|
||||
ttl: "10m",
|
||||
},
|
||||
);
|
||||
at.addGrant({ roomJoin: true, room: roomName });
|
||||
|
||||
return await at.toJwt();
|
||||
};
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get("/token", async (req, res) => {
|
||||
res.send({
|
||||
token: await createToken(),
|
||||
});
|
||||
});
|
||||
|
||||
export default router;
|
||||
8
apps/dispatch-server/routes/router.ts
Normal file
8
apps/dispatch-server/routes/router.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Router } from "express";
|
||||
import livekitRouter from "./livekit";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use("/livekit", livekitRouter);
|
||||
|
||||
export default router;
|
||||
@@ -11,6 +11,7 @@ export const handle =
|
||||
selectedZone: string;
|
||||
}) => {
|
||||
const userId = socket.data.user.id; // User ID aus dem JWT-Token
|
||||
console.log("User connected to dispatch server");
|
||||
await pubClient.json.set(`dispatchers:${socket.id}`, "$", {
|
||||
logoffTime,
|
||||
selectedZone,
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export const ChangeRufgruppe = () => {
|
||||
return (
|
||||
<>
|
||||
<details className="dropdown">
|
||||
<summary className="dropdown flex items-center gap-1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
|
||||
/>
|
||||
</svg>
|
||||
<div className="badge badge-soft badge-success">1</div>
|
||||
</summary>
|
||||
<ul className="menu dropdown-content bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm">
|
||||
<li>
|
||||
<a>Rufgruppe 1</a>
|
||||
</li>
|
||||
<li>
|
||||
<a>Rufgruppe 2</a>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,12 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { ToggleTalkButton } from "../ToggleTalkButton";
|
||||
import { ChangeRufgruppe } from "../ChangeRufgruppe";
|
||||
import { Notifications } from "../Notifications";
|
||||
import Link from "next/link";
|
||||
import { Connection } from "../Connection";
|
||||
import { ThemeSwap } from "./_components/ThemeSwap";
|
||||
import { useState } from "react";
|
||||
import { Audio } from "./_components/Audio";
|
||||
|
||||
export default function Navbar() {
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
@@ -31,7 +31,7 @@ export default function Navbar() {
|
||||
<ToggleTalkButton />
|
||||
</li>
|
||||
<li>
|
||||
<ChangeRufgruppe />
|
||||
<Audio />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
LocalParticipant,
|
||||
LocalTrackPublication,
|
||||
Participant,
|
||||
RemoteParticipant,
|
||||
RemoteTrack,
|
||||
RemoteTrackPublication,
|
||||
Room,
|
||||
RoomEvent,
|
||||
Track,
|
||||
VideoPresets,
|
||||
} from "livekit-client";
|
||||
import { connectionStore } from "../../../../_store/connectionStore";
|
||||
|
||||
export const Audio = () => {
|
||||
const connection = connectionStore();
|
||||
const [token, setToken] = useState("");
|
||||
const [room, setRoom] = useState<Room | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchToken = async () => {
|
||||
const response = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_DISPATCH_SERVER_URL}/livekit/token`,
|
||||
);
|
||||
const data = await response.json();
|
||||
setToken(data.token);
|
||||
};
|
||||
|
||||
fetchToken();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const joinRoom = async () => {
|
||||
if (!connection.isConnected) return;
|
||||
/* if (!token) return;
|
||||
if (!process.env.NEXT_PUBLIC_LIVEKIT_URL)
|
||||
return console.error("NEXT_PUBLIC_LIVEKIT_URL not set");
|
||||
console.log("COnnecting to room", {
|
||||
token,
|
||||
url: process.env.NEXT_PUBLIC_LIVEKIT_URL,
|
||||
}); */
|
||||
const url = "ws://localhost:7880";
|
||||
const token =
|
||||
"eyJhbGciOiJIUzI1NiJ9.eyJ2aWRlbyI6eyJyb29tSm9pbiI6dHJ1ZSwicm9vbSI6InF1aWNrc3RhcnQtcm9vbSJ9LCJpc3MiOiJBUElBbnNHZHRkWXAySG8iLCJleHAiOjE3NDIxNDk3MzAsIm5iZiI6MCwic3ViIjoicXVpY2tzdGFydC11c2VybmFtZSJ9.MVFDpwvjCF_AXjL9Mg40TFoKukZ4F3vOVB4DI_TZhHM";
|
||||
console.log("Connecting to room", { token, url });
|
||||
const room = new Room({
|
||||
// automatically manage subscribed video quality
|
||||
adaptiveStream: true,
|
||||
|
||||
// optimize publishing bandwidth and CPU for published tracks
|
||||
dynacast: true,
|
||||
|
||||
// default capture settings
|
||||
videoCaptureDefaults: {
|
||||
resolution: VideoPresets.h720.resolution,
|
||||
},
|
||||
});
|
||||
|
||||
// pre-warm connection, this can be called as early as your page is loaded
|
||||
room.prepareConnection(url, token);
|
||||
|
||||
// set up event listeners
|
||||
room
|
||||
.on(RoomEvent.TrackSubscribed, handleTrackSubscribed)
|
||||
.on(RoomEvent.TrackUnsubscribed, handleTrackUnsubscribed)
|
||||
.on(RoomEvent.ActiveSpeakersChanged, handleActiveSpeakerChange)
|
||||
.on(RoomEvent.Disconnected, handleDisconnect)
|
||||
.on(RoomEvent.LocalTrackUnpublished, handleLocalTrackUnpublished);
|
||||
|
||||
// connect to room
|
||||
await room.connect(url, token);
|
||||
console.log("connected to room", room.name);
|
||||
|
||||
// publish local camera and mic tracks
|
||||
await room.localParticipant.enableCameraAndMicrophone();
|
||||
|
||||
function handleTrackSubscribed(
|
||||
track: RemoteTrack,
|
||||
publication: RemoteTrackPublication,
|
||||
participant: RemoteParticipant,
|
||||
) {
|
||||
if (
|
||||
track.kind === Track.Kind.Video ||
|
||||
track.kind === Track.Kind.Audio
|
||||
) {
|
||||
// attach it to a new HTMLVideoElement or HTMLAudioElement
|
||||
const element = track.attach();
|
||||
}
|
||||
}
|
||||
|
||||
function handleTrackUnsubscribed(
|
||||
track: RemoteTrack,
|
||||
publication: RemoteTrackPublication,
|
||||
participant: RemoteParticipant,
|
||||
) {
|
||||
// remove tracks from all attached elements
|
||||
track.detach();
|
||||
}
|
||||
|
||||
function handleLocalTrackUnpublished(
|
||||
publication: LocalTrackPublication,
|
||||
participant: LocalParticipant,
|
||||
) {
|
||||
// when local tracks are ended, update UI to remove them from rendering
|
||||
publication.track?.detach();
|
||||
}
|
||||
|
||||
function handleActiveSpeakerChange(speakers: Participant[]) {
|
||||
// show UI indicators when participant is speaking
|
||||
}
|
||||
|
||||
function handleDisconnect() {
|
||||
console.log("disconnected from room");
|
||||
}
|
||||
setRoom(room);
|
||||
};
|
||||
|
||||
joinRoom();
|
||||
|
||||
return () => {
|
||||
room?.disconnect();
|
||||
};
|
||||
}, [token, connection.isConnected]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<details className="dropdown">
|
||||
<summary className="dropdown flex items-center gap-1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
|
||||
/>
|
||||
</svg>
|
||||
<div className="badge badge-soft badge-success">1</div>
|
||||
</summary>
|
||||
<ul className="menu dropdown-content bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm">
|
||||
<li>
|
||||
<a>Rufgruppe 1</a>
|
||||
</li>
|
||||
<li>
|
||||
<a>Rufgruppe 2</a>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -11,10 +11,13 @@
|
||||
"check-types": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@livekit/components-react": "^2.8.1",
|
||||
"@livekit/components-styles": "^1.1.4",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@repo/ui": "*",
|
||||
"@tailwindcss/postcss": "^4.0.14",
|
||||
"leaflet": "^1.9.4",
|
||||
"livekit-client": "^2.9.7",
|
||||
"next": "^15.1.0",
|
||||
"next-auth": "^4.24.11",
|
||||
"postcss": "^8.5.1",
|
||||
|
||||
8
apps/mediasoup-server/.d.ts
vendored
Normal file
8
apps/mediasoup-server/.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
declare module "next-auth/jwt" {
|
||||
interface JWT {
|
||||
uid: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
email: string;
|
||||
}
|
||||
}
|
||||
4
apps/mediasoup-server/.env.example
Normal file
4
apps/mediasoup-server/.env.example
Normal file
@@ -0,0 +1,4 @@
|
||||
PORT=3002
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
DISPATCH_APP_TOKEN=
|
||||
31
apps/mediasoup-server/index.ts
Normal file
31
apps/mediasoup-server/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import "dotenv/config";
|
||||
import express from "express";
|
||||
import { createServer } from "http";
|
||||
import { Server } from "socket.io";
|
||||
import { createAdapter } from "@socket.io/redis-adapter";
|
||||
import { jwtMiddleware } from "modules/socketJWTmiddleware";
|
||||
import { pubClient, subClient } from "modules/redis";
|
||||
|
||||
const app = express();
|
||||
const server = createServer(app);
|
||||
|
||||
pubClient.keys("dispatchers*").then((keys) => {
|
||||
if (!keys) return;
|
||||
keys.forEach(async (key) => {
|
||||
await pubClient.json.del(key);
|
||||
});
|
||||
});
|
||||
|
||||
const io = new Server(server, {
|
||||
adapter: createAdapter(pubClient, subClient),
|
||||
cors: {},
|
||||
});
|
||||
|
||||
io.use(jwtMiddleware);
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
console.log("User conencted to mediasoup server");
|
||||
});
|
||||
server.listen(process.env.PORT, () => {
|
||||
console.log(`Server running on port ${process.env.PORT}`);
|
||||
});
|
||||
317
apps/mediasoup-server/modules/mediasoup/Channel.ts
Normal file
317
apps/mediasoup-server/modules/mediasoup/Channel.ts
Normal file
@@ -0,0 +1,317 @@
|
||||
import { Worker } from 'mediasoup/node/lib/Worker';
|
||||
import { types as MediasoupTypes } from 'mediasoup';
|
||||
import logger from 'modules/winston/logger';
|
||||
import { Router } from 'mediasoup/node/lib/Router';
|
||||
import { DtlsParameters, WebRtcTransport } from 'mediasoup/node/lib/WebRtcTransport';
|
||||
import { Socket } from 'socket.io';
|
||||
import { UserDocument } from 'models/user';
|
||||
import { MediaKind, RtpCapabilities, RtpParameters } from 'mediasoup/node/lib/RtpParameters';
|
||||
import { EventEmitter } from 'stream';
|
||||
import { ServerTransportParams } from '@common/types/mediasoup';
|
||||
import { ConsumerOptions } from 'mediasoup-client/lib/Consumer';
|
||||
import { routerOptions, webRtcTransportConfig } from './config';
|
||||
|
||||
/* *
|
||||
* Represents a Voice-Channel: all transports, Producers and Consumers are managed using this Class
|
||||
* Does NOT manages events for when a user joins/ leaves the channel
|
||||
*/
|
||||
|
||||
export class MediasoupChannel extends EventEmitter {
|
||||
worker: Worker;
|
||||
|
||||
router: Router | undefined;
|
||||
|
||||
transports: WebRtcTransport[] = [];
|
||||
|
||||
producers: MediasoupTypes.Producer[] = [];
|
||||
|
||||
consumers: MediasoupTypes.Consumer[] = [];
|
||||
|
||||
constructor(worker: Worker) {
|
||||
super();
|
||||
this.worker = worker;
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.router = await this.worker.createRouter(/* routerOptions */ routerOptions);
|
||||
}
|
||||
|
||||
handleSocket(socket: Socket, user: UserDocument) {
|
||||
if (!this.router || !this.worker) {
|
||||
logger.warn('Rejected user connection: channel not yet initialized', { system: 'mediasoup' });
|
||||
return socket.emit('error-message', { error: 'channel not yet initialized' });
|
||||
}
|
||||
if (this.producers.length >= Number(process.env.MAX_VOICE_CONNECTIONS)) {
|
||||
logger.warn('Rejected user connection: to many connections', { system: 'mediasoup' });
|
||||
return socket.emit('error-message', { error: 'to many connections', system: 'mediasoup' });
|
||||
}
|
||||
socket.emit('sfu-ready');
|
||||
|
||||
socket.on('get-rtp-capabilities', (callback) => {
|
||||
const { rtpCapabilities } = this.router!;
|
||||
|
||||
callback(rtpCapabilities);
|
||||
});
|
||||
|
||||
socket.on('create-webrtc-transport', async (callback) => {
|
||||
try {
|
||||
const transport = await this.createWebRtcTransport();
|
||||
this.transports.push(transport);
|
||||
|
||||
// how to delete unused transports which never connected to a client?
|
||||
// For now:
|
||||
socket.once('disconnect', () => {
|
||||
transport.close();
|
||||
this.transports = this.transports.filter((t) => t.id !== transport.id);
|
||||
});
|
||||
|
||||
// send the parameters for the created transport back to the client
|
||||
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#TransportOptions
|
||||
callback({
|
||||
id: transport.id,
|
||||
iceParameters: transport.iceParameters,
|
||||
iceCandidates: transport.iceCandidates,
|
||||
dtlsParameters: transport.dtlsParameters
|
||||
} as ServerTransportParams);
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
callback({
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on(
|
||||
'transport-connect',
|
||||
async ({ dtlsParameters, transportId }: { dtlsParameters: DtlsParameters; transportId: string }) => {
|
||||
try {
|
||||
const transport = this.transports.find((t) => t.id === transportId);
|
||||
|
||||
transport?.on('@close', () => {
|
||||
this.transports = this.transports.filter((t) => t.id !== transportId);
|
||||
});
|
||||
|
||||
if (!transport) return;
|
||||
await transport.connect({ dtlsParameters });
|
||||
} catch (error) {
|
||||
logger.warn('cannot connect transport', { service: 'mediasoup' });
|
||||
socket.emit('error-message', { error: (error as Error).message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
socket.on(
|
||||
'transport-produce',
|
||||
async (
|
||||
{
|
||||
kind,
|
||||
rtpParameters,
|
||||
transportId
|
||||
}: { kind: MediaKind; rtpParameters: RtpParameters; transportId: string },
|
||||
callback
|
||||
) => {
|
||||
try {
|
||||
const transport = this.transports.find((t) => t.id === transportId);
|
||||
if (!transport) throw Error('transport not found');
|
||||
const producer = await transport.produce({
|
||||
kind,
|
||||
rtpParameters,
|
||||
paused: true
|
||||
});
|
||||
// DEBUG
|
||||
this.producers.push(producer);
|
||||
this.emit('new-producer', { producerId: producer.id, user: user.getPublicUser() });
|
||||
|
||||
producer.appData.user = user.getPublicUser();
|
||||
producer.observer.on('pause', () => {
|
||||
this.emit('producer-paused', { producerId: producer.id });
|
||||
});
|
||||
|
||||
producer.observer.on('resume', () => {
|
||||
this.emit('producer-resumed', { producerId: producer.id });
|
||||
});
|
||||
|
||||
producer.on('transportclose', () => {
|
||||
this.producers = this.producers.filter((p) => p.id !== producer.id);
|
||||
this.emit('producer-closed', { producerId: producer.id });
|
||||
producer.close();
|
||||
});
|
||||
|
||||
// Send back to the client the Producer's id
|
||||
callback({
|
||||
id: producer.id
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn(`Error while creating Producer on Transport! ${err}`, { service: 'mediasoup' });
|
||||
const error = err as Error;
|
||||
callback({ error: error.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
socket.on(
|
||||
'transport-consume',
|
||||
async (
|
||||
{
|
||||
rtpCapabilities,
|
||||
producerId,
|
||||
transportId
|
||||
}: {
|
||||
rtpCapabilities: RtpCapabilities;
|
||||
producerId: string;
|
||||
transportId: string;
|
||||
},
|
||||
callback
|
||||
) => {
|
||||
try {
|
||||
const transport = this.transports.find((t) => t.id === transportId);
|
||||
if (!transport) {
|
||||
socket.disconnect();
|
||||
throw Error('transport not found');
|
||||
}
|
||||
|
||||
// check if the router can consume the specified producer
|
||||
if (
|
||||
this.router!.canConsume({
|
||||
producerId,
|
||||
rtpCapabilities
|
||||
})
|
||||
) {
|
||||
// transport can now consume and return a consumer
|
||||
const producer = this.producers.find((p) => p.id === producerId);
|
||||
|
||||
if (!producer) {
|
||||
socket.disconnect();
|
||||
throw Error(`producer ${producerId} not found`);
|
||||
}
|
||||
const consumer = await transport.consume({
|
||||
producerId,
|
||||
rtpCapabilities,
|
||||
appData: { user, producerId },
|
||||
paused: producer.paused // Important: otherwise remote video stays black, no audio
|
||||
});
|
||||
|
||||
this.consumers.push(consumer);
|
||||
|
||||
// Cannot detect when consumer is closed dirrectly
|
||||
// Assuming that:
|
||||
// 1. when producer is closed, consumer is also closed
|
||||
// 2. when transport is closed, consumer is also closed
|
||||
// 2. and when socket is closed, consumer is also closed
|
||||
consumer.on('transportclose', () => {
|
||||
consumer.close();
|
||||
});
|
||||
|
||||
consumer.observer.on('close', () => {
|
||||
this.consumers = this.consumers.filter((c) => c.id !== consumer.id);
|
||||
});
|
||||
|
||||
// Bad, because it adds a listener for each consumer
|
||||
/* socket.once('disconnect', () => {
|
||||
consumer.close();
|
||||
this.consumers = this.consumers.filter((c) => c.id !== consumer.id);
|
||||
}); */
|
||||
|
||||
consumer.on('producerclose', () => {
|
||||
consumer.close();
|
||||
});
|
||||
|
||||
// from the consumer extract the following params
|
||||
// to send back to the Client
|
||||
const consumerOptions: ConsumerOptions = {
|
||||
id: consumer.id,
|
||||
producerId,
|
||||
kind: consumer.kind,
|
||||
rtpParameters: consumer.rtpParameters,
|
||||
appData: { user }
|
||||
};
|
||||
|
||||
// send the parameters to the client
|
||||
callback({ consumerOptions });
|
||||
} else {
|
||||
logger.warn(`Cannot consume producer with id: ${producerId}`, { service: 'mediasoup' });
|
||||
callback({
|
||||
params: {
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
logger.error(`error while creating consumer ${error}`, { service: 'Mediasoup' });
|
||||
callback({
|
||||
error
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
socket.on('consumer-resume', async ({ consumerId }: { consumerId: string }, resumeCallback) => {
|
||||
logger.silly(`Resuming consumer with id ${consumerId}`, { service: 'mediasoup' });
|
||||
const consumer = this.consumers.find((c) => c.id === consumerId);
|
||||
if (!consumer) return resumeCallback({ error: 'consumer not found' });
|
||||
await consumer.resume();
|
||||
if (typeof resumeCallback === 'function') {
|
||||
resumeCallback();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
socket.on('consumer-pause', async ({ consumerId }: { consumerId: string }, pauseCallback) => {
|
||||
logger.silly(`Pausing consumer with id ${consumerId}`, { service: 'mediasoup' });
|
||||
const consumer = this.consumers.find((c) => c.id === consumerId);
|
||||
if (!consumer) return pauseCallback({ error: 'consumer not found' });
|
||||
await consumer.pause();
|
||||
if (typeof pauseCallback === 'function') {
|
||||
pauseCallback();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
socket.on(
|
||||
'producer-resume',
|
||||
async ({ producerId, source }: { producerId: string; source: string }, resumeCallback) => {
|
||||
logger.silly(`Resuming producer with id ${producerId}`, { service: 'mediasoup' });
|
||||
|
||||
const producer = this.producers.find((p) => p.id === producerId);
|
||||
if (!producer) return resumeCallback({ error: 'producer not found' });
|
||||
if (source === 'admin') {
|
||||
this.producers.forEach((p) => p.pause());
|
||||
this.emit('producer-pausing-forced', { user: user.getPublicUser() });
|
||||
}
|
||||
await producer.resume();
|
||||
if (resumeCallback) {
|
||||
resumeCallback();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
);
|
||||
socket.on('producer-pause', async ({ producerId }: { producerId: string }, pauseCallback) => {
|
||||
logger.silly(`Pausing producer with id ${producerId}`, { service: 'mediasoup' });
|
||||
const producer = this.producers.find((p) => p.id === producerId);
|
||||
if (!producer) return pauseCallback({ error: 'producer not found' });
|
||||
await producer.pause();
|
||||
|
||||
if (typeof pauseCallback === 'function') {
|
||||
pauseCallback();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async createWebRtcTransport(): Promise<WebRtcTransport> {
|
||||
// https://mediasoup.org/documentation/v3/mediasoup/api/#router-createWebRtcTransport
|
||||
const transport = await this.router!.createWebRtcTransport(webRtcTransportConfig);
|
||||
transport.on('dtlsstatechange', (dtlsState) => {
|
||||
// Not reliable
|
||||
logger.silly('transport dtlsstatechange', { dtlsState, service: 'mediasoup' });
|
||||
if (dtlsState === 'closed') {
|
||||
transport.close();
|
||||
this.transports = this.transports.filter((t) => t.id !== transport.id);
|
||||
}
|
||||
});
|
||||
|
||||
return transport;
|
||||
}
|
||||
}
|
||||
51
apps/mediasoup-server/modules/mediasoup/config.ts
Normal file
51
apps/mediasoup-server/modules/mediasoup/config.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import os from 'os';
|
||||
import { types as mediasoupTypes } from 'mediasoup';
|
||||
|
||||
export const workerSettings: mediasoupTypes.WorkerSettings = {
|
||||
rtcMinPort: 2000,
|
||||
rtcMaxPort: 2300,
|
||||
logLevel: 'debug',
|
||||
logTags: ['info', 'ice', 'dtls', 'rtp', 'srtp', 'rtcp', 'message']
|
||||
};
|
||||
|
||||
export const routerOptions: mediasoupTypes.RouterOptions = {
|
||||
mediaCodecs: [
|
||||
{
|
||||
kind: 'audio',
|
||||
mimeType: 'audio/opus',
|
||||
clockRate: 48000,
|
||||
channels: 2
|
||||
},
|
||||
{
|
||||
kind: 'video',
|
||||
mimeType: 'video/VP8',
|
||||
clockRate: 90000,
|
||||
parameters: {
|
||||
'x-google-start-bitrate': 1000
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const webRtcTransportConfig: mediasoupTypes.WebRtcTransportOptions = {
|
||||
// https://mediasoup.org/documentation/v3/mediasoup/api/#WebRtcTransportOptions
|
||||
listenInfos: [
|
||||
{
|
||||
protocol: 'tcp',
|
||||
ip: '0.0.0.0',
|
||||
announcedIp: process.env.MEDIASOUP_ANOUNCE_IP // public ip
|
||||
},
|
||||
{
|
||||
protocol: 'udp',
|
||||
ip: '0.0.0.0',
|
||||
announcedIp: process.env.MEDIASOUP_ANOUNCE_IP // public ip
|
||||
}
|
||||
],
|
||||
enableUdp: true,
|
||||
enableTcp: true,
|
||||
preferUdp: true
|
||||
};
|
||||
|
||||
export default {
|
||||
numWorkers: Object.keys(os.cpus()).length
|
||||
};
|
||||
21
apps/mediasoup-server/modules/mediasoup/worker.ts
Normal file
21
apps/mediasoup-server/modules/mediasoup/worker.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { createWorker as mediasoupCreateWorker } from 'mediasoup';
|
||||
import logger from 'modules/winston/logger';
|
||||
import { workerSettings } from './config';
|
||||
|
||||
/**
|
||||
* * For now, each channel uses its own worker
|
||||
* ! Do not create more Workers than the number of CPU-Cores
|
||||
*/
|
||||
|
||||
export const createWorker = async () => {
|
||||
const worker = await mediasoupCreateWorker(workerSettings);
|
||||
logger.info(`Mediasoup worker created`, { service: 'mediasoup' });
|
||||
|
||||
worker.on('died', (error) => {
|
||||
// This implies something serious happened, so kill the application
|
||||
logger.error(`Mediasoup worker crashed! ${error}`, { error, service: 'mediasoup' });
|
||||
setTimeout(() => process.exit(1), 2000); // exit in 2 seconds
|
||||
});
|
||||
|
||||
return worker;
|
||||
};
|
||||
8
apps/mediasoup-server/modules/redis.ts
Normal file
8
apps/mediasoup-server/modules/redis.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { createClient } from "redis";
|
||||
|
||||
export const pubClient = createClient();
|
||||
export const subClient = pubClient.duplicate();
|
||||
|
||||
Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
|
||||
console.log("Redis connected");
|
||||
});
|
||||
28
apps/mediasoup-server/modules/socketJWTmiddleware.ts
Normal file
28
apps/mediasoup-server/modules/socketJWTmiddleware.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { ExtendedError, Server, Socket } from "socket.io";
|
||||
import { prisma } from "@repo/db";
|
||||
if (!process.env.DISPATCH_APP_TOKEN)
|
||||
throw new Error("DISPATCH_APP_TOKEN is not defined");
|
||||
|
||||
export const jwtMiddleware = async (
|
||||
socket: Socket,
|
||||
next: (err?: ExtendedError) => void,
|
||||
) => {
|
||||
try {
|
||||
const { uid } = socket.handshake.auth;
|
||||
if (!uid) return new Error("Authentication error");
|
||||
/* const token = socket.handshake.auth?.token;
|
||||
if (!token) return new Error("Authentication error");
|
||||
const decoded = jwt.verify(token, process.env.DISPATCH_APP_TOKEN!); */
|
||||
// socket.data.userId = decoded.; // User ID lokal speichern
|
||||
const user = await prisma.user.findUniqueOrThrow({
|
||||
where: { id: uid },
|
||||
});
|
||||
|
||||
socket.data.user = user;
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
next(new Error("Authentication error"));
|
||||
}
|
||||
};
|
||||
5
apps/mediasoup-server/nodemon.json
Normal file
5
apps/mediasoup-server/nodemon.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"watch": ["."],
|
||||
"ext": "ts",
|
||||
"exec": "tsx index.ts"
|
||||
}
|
||||
33
apps/mediasoup-server/package.json
Normal file
33
apps/mediasoup-server/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "mediasoup-server",
|
||||
"exports": {
|
||||
"helpers": "./helper"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "nodemon",
|
||||
"build": "tsc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/db": "*",
|
||||
"@repo/typescript-config": "*",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "^22.13.5",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"concurrently": "^9.1.2",
|
||||
"typescript": "latest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-email/components": "^0.0.33",
|
||||
"@redis/json": "^1.0.7",
|
||||
"@socket.io/redis-adapter": "^8.3.0",
|
||||
"axios": "^1.7.9",
|
||||
"cron": "^4.1.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"nodemailer": "^6.10.0",
|
||||
"react": "^19.0.0",
|
||||
"redis": "^4.7.0",
|
||||
"socket.io": "^4.8.1"
|
||||
}
|
||||
}
|
||||
158
apps/mediasoup-server/socket-events/sfu.ts
Normal file
158
apps/mediasoup-server/socket-events/sfu.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Worker } from 'mediasoup/node/lib/types';
|
||||
import { UserDocument } from 'models/user';
|
||||
import { MediasoupChannel } from 'modules/mediasoup/Channel';
|
||||
import { createWorker } from 'modules/mediasoup/worker';
|
||||
import { Socket } from 'socket.io';
|
||||
import { User } from '@common/types/user';
|
||||
import { EventEmitter } from 'stream';
|
||||
import { setMemberNickname } from 'modules/bot/bot';
|
||||
import PilotController from './pilot';
|
||||
|
||||
interface SfuClient {
|
||||
socket: Socket;
|
||||
channelId: string;
|
||||
user: UserDocument;
|
||||
}
|
||||
|
||||
interface ISfuController {
|
||||
worker: Worker[];
|
||||
observer: EventEmitter;
|
||||
clients: Map<string, SfuClient>;
|
||||
channel: Map<string, MediasoupChannel>;
|
||||
init: () => void;
|
||||
handle: (channelId: string, socket: Socket, user: UserDocument) => void;
|
||||
disconnectUser: (userId: string) => void;
|
||||
}
|
||||
|
||||
const SfuController: ISfuController = {
|
||||
clients: new Map(),
|
||||
channel: new Map(),
|
||||
observer: new EventEmitter(),
|
||||
worker: [],
|
||||
disconnectUser: async (userId: string) => {
|
||||
SfuController.clients.forEach((client) => {
|
||||
if (client.user._id.toString() === userId) {
|
||||
client.socket.emit('error-message', { error: 'DISCONNECTED_BY_ADMIN' });
|
||||
|
||||
client.socket.disconnect();
|
||||
SfuController.clients.delete(client.socket.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
init: async () => {
|
||||
// Setup Worker and Channel
|
||||
// Scalable!
|
||||
SfuController.worker = [
|
||||
await createWorker(),
|
||||
await createWorker(),
|
||||
await createWorker(),
|
||||
await createWorker(),
|
||||
await createWorker()
|
||||
];
|
||||
SfuController.channel.set('1', new MediasoupChannel(SfuController.worker[0])); // LST_VAR_RD_01
|
||||
SfuController.channel.set('2', new MediasoupChannel(SfuController.worker[1])); // LST_VAR_RD_02
|
||||
SfuController.channel.set('3', new MediasoupChannel(SfuController.worker[2])); // LST_VAR_RD_03
|
||||
SfuController.channel.set('4', new MediasoupChannel(SfuController.worker[3])); // LST_VAR_RD_04
|
||||
SfuController.channel.set('x', new MediasoupChannel(SfuController.worker[4])); // LST_VAR_RESERVE
|
||||
|
||||
// setup listends for new connections, producer-close event is handled by Channel
|
||||
SfuController.channel.forEach((channel, channelId) => {
|
||||
channel.on('producer-pausing-forced', ({ user }: { user: User.PublicUser }) => {
|
||||
SfuController.clients.forEach((client) => {
|
||||
if (client.channelId === channelId && client.user._id.toString() !== user.id) {
|
||||
client.socket.emit('producer-pausing-forced', { user });
|
||||
}
|
||||
});
|
||||
});
|
||||
channel.on('new-producer', ({ producerId, user }) => {
|
||||
SfuController.clients.forEach((client) => {
|
||||
if (client.channelId === channelId) {
|
||||
client.socket.emit('new-producer', { producerId, user });
|
||||
}
|
||||
});
|
||||
});
|
||||
channel.on('producer-closed', ({ producerId }) => {
|
||||
SfuController.clients.forEach((client) => {
|
||||
if (client.channelId === channelId) {
|
||||
client.socket.emit('producer-closed', { producerId });
|
||||
}
|
||||
});
|
||||
});
|
||||
channel.on('producer-paused', ({ producerId }) => {
|
||||
SfuController.clients.forEach((client) => {
|
||||
if (client.channelId === channelId) {
|
||||
client.socket.emit('producer-paused', { producerId });
|
||||
}
|
||||
});
|
||||
});
|
||||
channel.on('producer-resumed', ({ producerId }) => {
|
||||
SfuController.clients.forEach((client) => {
|
||||
if (client.channelId === channelId) {
|
||||
client.socket.emit('producer-resumed', { producerId });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
handle: (channelId, socket, user) => {
|
||||
const channel = SfuController.channel.get(channelId);
|
||||
if (!channel) {
|
||||
socket.emit('error-message', { error: 'invalid channel id' });
|
||||
return;
|
||||
}
|
||||
|
||||
// check for double connections
|
||||
if (
|
||||
Array.from(SfuController.clients.values()).find(
|
||||
(client) => client.user._id.toString() === user._id.toString()
|
||||
) &&
|
||||
process.env.ALLOW_DOUBLE_CONNECTION === 'false'
|
||||
) {
|
||||
socket.emit('error-message', { error: 'DOUBLE_CONNECTION' });
|
||||
return;
|
||||
}
|
||||
|
||||
SfuController.clients.set(socket.id, { socket, channelId, user });
|
||||
|
||||
// Update Discord username for dispatcher
|
||||
SfuController.observer.emit('channel-changed', channelId, user);
|
||||
|
||||
const userInPilot = PilotController.clients.get(user._id.toString());
|
||||
|
||||
if (userInPilot) {
|
||||
userInPilot.voiceChannel = channelId;
|
||||
PilotController.clients.set(user._id.toString(), userInPilot);
|
||||
PilotController.observer.emit('stations-changed');
|
||||
}
|
||||
|
||||
channel.handleSocket(socket, user);
|
||||
|
||||
socket.on('get-producers', (getPcb) => {
|
||||
getPcb(channel.producers.map((p) => ({ producerId: p.id, user: p.appData.user, paused: p.paused })));
|
||||
});
|
||||
socket.on('disconnect', () => {
|
||||
socket.removeAllListeners();
|
||||
SfuController.clients.delete(socket.id);
|
||||
const userInPilotDisconnect = PilotController.clients.get(user._id.toString());
|
||||
|
||||
if (userInPilotDisconnect) {
|
||||
userInPilotDisconnect.voiceChannel = undefined;
|
||||
PilotController.clients.set(user._id.toString(), userInPilotDisconnect);
|
||||
PilotController.observer.emit('stations-changed');
|
||||
}
|
||||
});
|
||||
socket.on('set-should-transmit', (eventData: { shouldTransmit: boolean; source: string }) => {
|
||||
SfuController.clients.forEach((client) => {
|
||||
if (client.user._id.toString() === user._id.toString()) {
|
||||
client.socket.emit('set-should-transmit', eventData);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
SfuController.init();
|
||||
|
||||
export default SfuController;
|
||||
11
apps/mediasoup-server/tsconfig.json
Normal file
11
apps/mediasoup-server/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "@repo/typescript-config/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": ".",
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": ["**/*.ts", "./index.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -64,6 +64,21 @@ services:
|
||||
- moodle_data:/bitnami/moodle
|
||||
- moodle_moodledata:/bitnami/moodledata
|
||||
# Für den Zugriff auf den Host
|
||||
livekit-server:
|
||||
image: livekit/livekit-server
|
||||
container_name: livekit_server
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "7880:7880"
|
||||
- "7881:7881"
|
||||
- "7882:7882/udp"
|
||||
volumes:
|
||||
- "./livekit.yaml:/livekit.yaml"
|
||||
command:
|
||||
- "--config"
|
||||
- "/livekit.yaml"
|
||||
- "--node-ip=127.0.0.1"
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
moodle_data:
|
||||
|
||||
13
livekit.yaml
Normal file
13
livekit.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
port: 7880
|
||||
rtc:
|
||||
udp_port: 7882
|
||||
tcp_port: 7881
|
||||
use_external_ip: false
|
||||
enable_loopback_candidate: false
|
||||
ice_servers:
|
||||
- urls: ["stun:stun.l.google.com:19302"]
|
||||
keys:
|
||||
APIAnsGdtdYp2Ho: tdPjVsYUx8ddC7K9NvdmVAeLRF9GeADD6Fedm1x63fWC
|
||||
logging:
|
||||
json: false
|
||||
level: info
|
||||
448
package-lock.json
generated
448
package-lock.json
generated
@@ -21,10 +21,13 @@
|
||||
"apps/dispatch": {
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@livekit/components-react": "^2.8.1",
|
||||
"@livekit/components-styles": "^1.1.4",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@repo/ui": "*",
|
||||
"@tailwindcss/postcss": "^4.0.14",
|
||||
"leaflet": "^1.9.4",
|
||||
"livekit-client": "^2.9.7",
|
||||
"next": "^15.1.0",
|
||||
"next-auth": "^4.24.11",
|
||||
"postcss": "^8.5.1",
|
||||
@@ -52,15 +55,16 @@
|
||||
"@redis/json": "^1.0.7",
|
||||
"@socket.io/redis-adapter": "^8.3.0",
|
||||
"axios": "^1.7.9",
|
||||
"cors": "^2.8.5",
|
||||
"cron": "^4.1.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"livekit-server-sdk": "^2.10.2",
|
||||
"nodemailer": "^6.10.0",
|
||||
"react": "^19.0.0",
|
||||
"redis": "^4.7.0",
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-redis": "^6.1.1"
|
||||
"socket.io": "^4.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/db": "*",
|
||||
@@ -68,7 +72,6 @@
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "^22.13.5",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/socket.io-redis": "^3.0.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"typescript": "latest"
|
||||
}
|
||||
@@ -158,6 +161,31 @@
|
||||
"typescript": "latest"
|
||||
}
|
||||
},
|
||||
"apps/mediasoup-server": {
|
||||
"dependencies": {
|
||||
"@react-email/components": "^0.0.33",
|
||||
"@redis/json": "^1.0.7",
|
||||
"@socket.io/redis-adapter": "^8.3.0",
|
||||
"axios": "^1.7.9",
|
||||
"cron": "^4.1.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"nodemailer": "^6.10.0",
|
||||
"react": "^19.0.0",
|
||||
"redis": "^4.7.0",
|
||||
"socket.io": "^4.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/db": "*",
|
||||
"@repo/typescript-config": "*",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "^22.13.5",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"concurrently": "^9.1.2",
|
||||
"typescript": "latest"
|
||||
}
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||
@@ -451,6 +479,12 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/protobuf": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz",
|
||||
"integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==",
|
||||
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
@@ -1734,6 +1768,108 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@livekit/components-core": {
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/components-core/-/components-core-0.12.1.tgz",
|
||||
"integrity": "sha512-R7qWoVzPckOYxEHZgP3Kp8u+amu+isnTptgoZV7+bpmLRBHI7mWnaD+0uDWlyIMjI1pBbK3wHg0ILKa5UytI+A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "1.6.11",
|
||||
"loglevel": "1.9.1",
|
||||
"rxjs": "7.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"livekit-client": "^2.8.1",
|
||||
"tslib": "^2.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@livekit/components-core/node_modules/@floating-ui/dom": {
|
||||
"version": "1.6.11",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz",
|
||||
"integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.6.0",
|
||||
"@floating-ui/utils": "^0.2.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@livekit/components-core/node_modules/rxjs": {
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
||||
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@livekit/components-react": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/components-react/-/components-react-2.8.1.tgz",
|
||||
"integrity": "sha512-XpuDu7iDMcN4pkV8CYNzHf9hLNdYOeEtbmCr7Zesy6Au3BxUl4aS1Ajmg0b75Rx7zTlkyCJt9Lm4VrEqbJCI6Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@livekit/components-core": "0.12.1",
|
||||
"clsx": "2.1.1",
|
||||
"usehooks-ts": "3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@livekit/krisp-noise-filter": "^0.2.12",
|
||||
"livekit-client": "^2.8.1",
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@livekit/krisp-noise-filter": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@livekit/components-react/node_modules/usehooks-ts": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.0.tgz",
|
||||
"integrity": "sha512-bBIa7yUyPhE1BCc0GmR96VU/15l/9gP1Ch5mYdLcFBaFGQsdmXkvjV0TtOqW1yUd6VjIwDunm+flSciCQXujiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash.debounce": "^4.0.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.15.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/@livekit/components-styles": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/components-styles/-/components-styles-1.1.4.tgz",
|
||||
"integrity": "sha512-QCupn7tQ/dy/WZclrfsgtDe8peiGYS6Ied1IGkKOysaXo04l90t62SIUTKyxgd0dNDhUDC0p34qCggGZs/44lQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@livekit/mutex": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/mutex/-/mutex-1.1.1.tgz",
|
||||
"integrity": "sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@livekit/protocol": {
|
||||
"version": "1.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.34.0.tgz",
|
||||
"integrity": "sha512-bU7pCLAMRVTVZb1KSxA46q55bhOc4iATrY/gccy2/oX1D57tiZEI+8wGRWHeDwBb0UwnABu6JXzC4tTFkdsaOg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@next-auth/prisma-adapter": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@next-auth/prisma-adapter/-/prisma-adapter-1.0.7.tgz",
|
||||
@@ -3233,17 +3369,6 @@
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/socket.io-redis": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/socket.io-redis/-/socket.io-redis-3.0.0.tgz",
|
||||
"integrity": "sha512-AFINYd5w7LwUqUDZAfmv4AEhXOJ0YsXCQf11RRDok+Zq7cCyhTMgFAbWSwUScP/JEYD4KF6+4O4SiussjrKbmQ==",
|
||||
"deprecated": "This is a stub types definition. socket.io-redis provides its own type definitions, so you do not need this installed.",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"socket.io-redis": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/through": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz",
|
||||
@@ -4543,6 +4668,48 @@
|
||||
"upper-case": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz",
|
||||
"integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase-keys": {
|
||||
"version": "9.1.3",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-9.1.3.tgz",
|
||||
"integrity": "sha512-Rircqi9ch8AnZscQcsA1C47NFdaO3wukpmIRzYcDOrmvgt78hM/sj5pZhZNec2NM12uk5vTwRHZ4anGcrC4ZTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"camelcase": "^8.0.0",
|
||||
"map-obj": "5.0.0",
|
||||
"quick-lru": "^6.1.1",
|
||||
"type-fest": "^4.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase-keys/node_modules/type-fest": {
|
||||
"version": "4.37.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.37.0.tgz",
|
||||
"integrity": "sha512-S/5/0kFftkq27FPNye0XM1e2NsnoD/3FS+pBmbjmmtLT6I+i344KoOf7pvXreaFsDamWeaJX55nczA1m5PsBDg==",
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001704",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001704.tgz",
|
||||
@@ -5449,15 +5616,6 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/denque": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz",
|
||||
"integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@@ -6700,7 +6858,6 @@
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
@@ -9408,6 +9565,60 @@
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/livekit-client": {
|
||||
"version": "2.9.7",
|
||||
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.9.7.tgz",
|
||||
"integrity": "sha512-a+Y76HE5k7IaFOpDnr14ON+VOAgh7cCjuBq8Loq5p5xHZzw+/cQyX/xPsMLU4lloKO5zGf45YZJYt/Egk1Xg+g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@livekit/mutex": "1.1.1",
|
||||
"@livekit/protocol": "1.34.0",
|
||||
"events": "^3.3.0",
|
||||
"loglevel": "^1.9.2",
|
||||
"sdp-transform": "^2.15.0",
|
||||
"ts-debounce": "^4.0.0",
|
||||
"tslib": "2.8.1",
|
||||
"typed-emitter": "^2.1.0",
|
||||
"webrtc-adapter": "^9.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/livekit-client/node_modules/loglevel": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz",
|
||||
"integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/loglevel"
|
||||
}
|
||||
},
|
||||
"node_modules/livekit-server-sdk": {
|
||||
"version": "2.10.2",
|
||||
"resolved": "https://registry.npmjs.org/livekit-server-sdk/-/livekit-server-sdk-2.10.2.tgz",
|
||||
"integrity": "sha512-XDoHvLY9a6DXM7Iit7XdNp1M9OK/idWHuqZnKAoirBbPmaFmlAVKeQGQIZTG7UrktgoRAPvu3vv0UdC6Ds80Ng==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^1.7.2",
|
||||
"@livekit/protocol": "^1.32.1",
|
||||
"camelcase-keys": "^9.0.0",
|
||||
"jose": "^5.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/livekit-server-sdk/node_modules/jose": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
|
||||
"integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
},
|
||||
"node_modules/loader-runner": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
|
||||
@@ -9440,6 +9651,12 @@
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.get": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||
@@ -9510,6 +9727,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/loglevel": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz",
|
||||
"integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/loglevel"
|
||||
}
|
||||
},
|
||||
"node_modules/longest-streak": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
|
||||
@@ -9583,6 +9813,18 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/map-obj": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-5.0.0.tgz",
|
||||
"integrity": "sha512-2L3MIgJynYrZ3TYMriLDLWocz15okFakV6J12HXvMXDHui2x/zgChzg1u9mFFGbbGWE+GsLpQByt4POb9Or+uA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-table": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
|
||||
@@ -9917,6 +10159,10 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mediasoup-server": {
|
||||
"resolved": "apps/mediasoup-server",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
@@ -14427,6 +14673,18 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/quick-lru": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.2.tgz",
|
||||
"integrity": "sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
@@ -14693,33 +14951,6 @@
|
||||
"@redis/time-series": "1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/redis-commands": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
|
||||
"integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/redis-errors": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/redis-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"redis-errors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/reflect.getprototypeof": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
||||
@@ -15244,7 +15475,7 @@
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
@@ -15390,6 +15621,21 @@
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sdp": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz",
|
||||
"integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sdp-transform": {
|
||||
"version": "2.15.0",
|
||||
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.15.0.tgz",
|
||||
"integrity": "sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"sdp-verify": "checker.js"
|
||||
}
|
||||
},
|
||||
"node_modules/selderee": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz",
|
||||
@@ -15863,76 +16109,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-redis": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-redis/-/socket.io-redis-6.1.1.tgz",
|
||||
"integrity": "sha512-jeaXe3TGKC20GMSlPHEdwTUIWUpay/L7m5+S9TQcOf22p9Llx44/RkpJV08+buXTZ8E+aivOotj2RdeFJJWJJQ==",
|
||||
"deprecated": "This package has been renamed to '@socket.io/redis-adapter', please see the migration guide here: https://socket.io/docs/v4/redis-adapter/#migrating-from-socketio-redis",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "~4.3.1",
|
||||
"notepack.io": "~2.2.0",
|
||||
"redis": "^3.0.0",
|
||||
"socket.io-adapter": "~2.2.0",
|
||||
"uid2": "0.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-redis/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-redis/node_modules/notepack.io": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-2.2.0.tgz",
|
||||
"integrity": "sha512-9b5w3t5VSH6ZPosoYnyDONnUTF8o0UkBw7JLA6eBlYJWyGT1Q3vQa8Hmuj1/X6RYvHjjygBDgw6fJhe0JEojfw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/socket.io-redis/node_modules/redis": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz",
|
||||
"integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"denque": "^1.5.0",
|
||||
"redis-commands": "^1.7.0",
|
||||
"redis-errors": "^1.2.0",
|
||||
"redis-parser": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-redis"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-redis/node_modules/socket.io-adapter": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.2.0.tgz",
|
||||
"integrity": "sha512-rG49L+FwaVEwuAdeBRq49M97YI3ElVabJPzvHT9S6a2CWhDKnjSFasvwAwSYPRhQzfn4NtDIbCaGYgOCOU/rlg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/socket.io-redis/node_modules/uid2": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz",
|
||||
"integrity": "sha512-5gSP1liv10Gjp8cMEnFd6shzkL/D6W1uhXSFNCxDC+YI8+L8wkCYCbJ7n77Ezb4wE/xzMogecE+DtamEe9PZjg=="
|
||||
},
|
||||
"node_modules/socket.io/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
@@ -16589,6 +16765,12 @@
|
||||
"typescript": ">=4.8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-debounce": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-debounce/-/ts-debounce-4.0.0.tgz",
|
||||
"integrity": "sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
@@ -16884,6 +17066,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/typed-emitter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz",
|
||||
"integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==",
|
||||
"license": "MIT",
|
||||
"optionalDependencies": {
|
||||
"rxjs": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
|
||||
@@ -17376,6 +17567,19 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webrtc-adapter": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-9.0.1.tgz",
|
||||
"integrity": "sha512-1AQO+d4ElfVSXyzNVTOewgGT/tAomwwztX/6e3totvyyzXPvXIIuUUjAmyZGbKBKbZOXauuJooZm3g6IuFuiNQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"sdp": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0",
|
||||
"npm": ">=3.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
11
test/livekit.yaml
Normal file
11
test/livekit.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
port: 7880
|
||||
rtc:
|
||||
udp_port: 7882
|
||||
tcp_port: 7881
|
||||
use_external_ip: false
|
||||
enable_loopback_candidate: false
|
||||
keys:
|
||||
APIAnsGdtdYp2Ho: tdPjVsYUx8ddC7K9NvdmVAeLRF9GeADD6Fedm1x63fWC
|
||||
logging:
|
||||
json: false
|
||||
level: info
|
||||
Reference in New Issue
Block a user