From bb8e5c4d0e8cb64bafd6337953a060818a9b1815 Mon Sep 17 00:00:00 2001 From: balazskreith Date: Wed, 30 Oct 2024 10:18:11 +0200 Subject: [PATCH] save --- .vscode/settings.json | 5 + .../CreateProducerRequestListener.ts | 10 ++ .../JoinCallRequestListener.ts | 17 ++- media-server/src/config.ts | 4 + media-server/src/main.ts | 2 + media-server/src/services/MediasoupService.ts | 55 +++++--- webapp/.gitignore | 3 +- webapp/src/App.tsx | 22 ++-- webapp/src/common/utils.ts | 121 +++++++++++++++++- webapp/src/components/Box.tsx | 55 +++++++- webapp/src/components/Section.tsx | 36 ++++++ webapp/src/signals/signals.tsx | 2 +- webapp/src/stores/LocalClientStore.tsx | 2 + webapp/src/utils/Connection.tsx | 5 +- webapp/src/views/Join.tsx | 21 +-- webapp/src/views/Main.tsx | 60 ++++++++- webapp/src/views/Menu.tsx | 4 +- webapp/src/views/VideoCall.tsx | 72 +++++++---- 18 files changed, 422 insertions(+), 74 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 webapp/src/components/Section.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..58aaa72 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + } +} diff --git a/media-server/src/client-listeners/CreateProducerRequestListener.ts b/media-server/src/client-listeners/CreateProducerRequestListener.ts index 578b621..637d674 100644 --- a/media-server/src/client-listeners/CreateProducerRequestListener.ts +++ b/media-server/src/client-listeners/CreateProducerRequestListener.ts @@ -10,6 +10,7 @@ const logger = createLogger('CreateProducerRequestListener'); export type CreateProducerRequestListenerContext = { mediasoupService: MediasoupService, clients: Map, + maxProducerPerClients: number, } export function createCreateProducerRequestListener(listenerContext: CreateProducerRequestListenerContext) { @@ -27,14 +28,23 @@ export function createCreateProducerRequestListener(listenerContext: CreateProdu return console.warn(`Invalid message type ${request.type}`); } else if (!client) { return console.warn(`Client ${messageContext.clientId} not found`); + } else if (!client.routerId) { + return console.warn(`Client ${messageContext.clientId} routerId not found`); } else if (client.sndTransport === undefined) { return console.warn(`Client ${messageContext.clientId} has no sending transport`); + } else if (client.mediaProducers.size >= listenerContext.maxProducerPerClients) { + return messageContext.send(new Response( + request.requestId, + undefined, + `Max producers per client reached` + )); } let response: CreateProducerResponsePayload | undefined; let error: string | undefined; try { + const producer = await client.sndTransport.produce({ kind: request.kind, rtpParameters: request.rtpParameters, diff --git a/media-server/src/client-listeners/JoinCallRequestListener.ts b/media-server/src/client-listeners/JoinCallRequestListener.ts index f76531c..adf564b 100644 --- a/media-server/src/client-listeners/JoinCallRequestListener.ts +++ b/media-server/src/client-listeners/JoinCallRequestListener.ts @@ -9,6 +9,7 @@ const logger = createLogger('JoinCallRequestListener'); export type JoinCallRequestListenerContext = { mediasoupService: MediasoupService; clients: Map; + maxTransportsPerRouter: number; } type TurnServerConfig = { @@ -37,12 +38,22 @@ export function createJoinCallRequestListener(listenerContext: JoinCallRequestLi logger.debug(`Client ${client.clientId} joining call ${request.callId}. request: %o`, request); + let closeClient = false; let response: JoinCallResponsePayload | undefined; let error: string | undefined; try { + if (request.callId && mediasoupService.routers.has(request.callId) === false) { + throw new Error(`Call with id ${request.callId} does not exist`); + } + const router = await mediasoupService.getOrCreateRouter(request.callId); - + + if (listenerContext.maxTransportsPerRouter <= router.appData.transports.size) { + closeClient = true; + throw new Error(`Max transports per router reached`); + } + client.routerId = router.id; const turnResponse = await (await fetch(`http://stunner-auth.stunner-system:8088?service=turn`)).json(); @@ -75,6 +86,10 @@ export function createJoinCallRequestListener(listenerContext: JoinCallRequestLi response, error )); + + if (closeClient) { + client.webSocket.close(); + } }; return result; } \ No newline at end of file diff --git a/media-server/src/config.ts b/media-server/src/config.ts index d0cd4eb..29f24a9 100644 --- a/media-server/src/config.ts +++ b/media-server/src/config.ts @@ -7,6 +7,8 @@ export type Config = { configPath?: string; server: ServerConfig, mediasoup: MediasoupServiceConfig; + maxTransportsPerRouter: number; + maxProducerPerClients: number; }; const getDefaultConfig: () => Config = () => { @@ -16,6 +18,8 @@ const getDefaultConfig: () => Config = () => { serverIp: '127.0.0.1', // announcedIp: '127.0.0.1', }, + maxTransportsPerRouter: 6, + maxProducerPerClients: 1, mediasoup: { numberOfWorkers: 1, workerSettings: { diff --git a/media-server/src/main.ts b/media-server/src/main.ts index b12c233..57701ca 100644 --- a/media-server/src/main.ts +++ b/media-server/src/main.ts @@ -35,6 +35,7 @@ const listeners = new Map() .set('join-call-request', createJoinCallRequestListener({ mediasoupService, clients, + maxTransportsPerRouter: config.maxTransportsPerRouter, })) .set('connect-transport-request', createConnectTransportRequestListener({ mediasoupService, @@ -66,6 +67,7 @@ const listeners = new Map() createCreateProducerRequestListener({ clients, mediasoupService, + maxProducerPerClients: config.maxProducerPerClients, }) ) .set( diff --git a/media-server/src/services/MediasoupService.ts b/media-server/src/services/MediasoupService.ts index 1e42352..7ce7550 100644 --- a/media-server/src/services/MediasoupService.ts +++ b/media-server/src/services/MediasoupService.ts @@ -16,8 +16,13 @@ type WorkerAppData = { webRtcServer?: mediasoup.types.WebRtcServer<{ port: number }>; } -type RouterAppData = { +export type RouterAppData = { workerPid: number; + mediaProducers: Map>; + mediaConsumers: Map>; + dataProducers: Map; + dataConsumers: Map; + transports: Map; } type ProducerAppData = { @@ -116,7 +121,7 @@ export class MediasoupService { } } - public async getOrCreateRouter(routerId?: string): Promise { + public async getOrCreateRouter(routerId?: string): Promise> { if (!this.workers) throw new Error('Worker is not started'); let router = this.routers.get(routerId || ''); @@ -124,26 +129,31 @@ export class MediasoupService { if (router) return router; const worker = [...this.workers.values()][Math.floor(Math.random() * this.workers.size)]; - router = await worker.createRouter({ + const newrouter = await worker.createRouter({ mediaCodecs: this.config.mediaCodecs, appData: { workerPid: worker.pid, + dataConsumers: new Map(), + dataProducers: new Map(), + mediaConsumers: new Map(), + mediaProducers: new Map(), + transports: new Map(), } }); - const addTransport = (transport: mediasoup.types.Transport) => this._addTransport(router!, transport); + const addTransport = (transport: mediasoup.types.Transport) => this._addTransport(newrouter, transport); - router.observer.once('close', () => { - router.observer.off('newtransport', addTransport); - this.routers.delete(router!.id); + newrouter.observer.once('close', () => { + newrouter.observer.off('newtransport', addTransport); + this.routers.delete(newrouter.id); - logger.info(`Router ${router!.id} closed`); + logger.info(`Router ${newrouter.id} closed`); }) - router.observer.on('newtransport', addTransport); - this.routers.set(router.id, router); + newrouter.observer.on('newtransport', addTransport); + this.routers.set(newrouter.id, newrouter); - logger.info(`Router ${router.id} created`); - return router; + logger.info(`Router ${newrouter.id} created`); + return newrouter; } public async consumeMediaProducer(producerId: string, consumingClient: ClientContext): Promise { @@ -198,7 +208,7 @@ export class MediasoupService { return consumer; }; - private _addTransport = (router: mediasoup.types.Router, transport: mediasoup.types.Transport) => { + private _addTransport = (router: mediasoup.types.Router, transport: mediasoup.types.Transport) => { const addProducer = (producer: mediasoup.types.Producer) => this._addProducer(router, transport, producer); const addConsumer = (consumer: mediasoup.types.Consumer) => this._addConsumer(router, transport, consumer); @@ -211,6 +221,7 @@ export class MediasoupService { transport.observer.off('newdataproducer', addDataProducer); transport.observer.off('newdataconsumer', addDataConsumer); + router.appData.transports.delete(transport.id); this.transports.delete(transport.id); logger.info(`Transport ${transport.id} closed on router ${router.id}. the number of transports is ${this.transports.size}`); @@ -225,14 +236,16 @@ export class MediasoupService { transport.observer.on('newdataproducer', addDataProducer); transport.observer.on('newdataconsumer', addDataConsumer); + router.appData.transports.set(transport.id, transport); this.transports.set(transport.id, transport); logger.info(`Transport ${transport.id} created on router ${router.id}`); } - private _addProducer = (router: mediasoup.types.Router, transport: mediasoup.types.Transport, producer: mediasoup.types.Producer) => { + private _addProducer = (router: mediasoup.types.Router, transport: mediasoup.types.Transport, producer: mediasoup.types.Producer) => { producer.observer.once('close', () => { + router.appData.mediaProducers.delete(producer.id); this.mediaProducers.delete(producer.id); logger.info(`Producer ${producer.id} closed on transport ${transport.id} on router ${router.id}`); @@ -241,39 +254,47 @@ export class MediasoupService { producer.appData.routerId = router.id; producer.appData.transportId = transport.id; producer.appData.remoteClosed = false; + + router.appData.mediaProducers.set(producer.id, producer as mediasoup.types.Producer); this.mediaProducers.set(producer.id, producer as mediasoup.types.Producer); logger.info(`Producer ${producer.id} created on transport ${transport.id} on router ${router.id}`); } - private _addConsumer = (router: mediasoup.types.Router, transport: mediasoup.types.Transport, consumer: mediasoup.types.Consumer) => { + private _addConsumer = (router: mediasoup.types.Router, transport: mediasoup.types.Transport, consumer: mediasoup.types.Consumer) => { consumer.observer.once('close', () => { + router.appData.mediaConsumers.delete(consumer.id); this.mediaConsumers.delete(consumer.id); logger.info(`Consumer ${consumer.id} closed on transport ${transport.id} on router ${router.id}`); }); + router.appData.mediaConsumers.set(consumer.id, consumer); this.mediaConsumers.set(consumer.id, consumer); logger.info(`Consumer ${consumer.id} created on transport ${transport.id} on router ${router.id}`); } - private _addDataProducer = (router: mediasoup.types.Router, transport: mediasoup.types.Transport, dataProducer: mediasoup.types.DataProducer) => { + private _addDataProducer = (router: mediasoup.types.Router, transport: mediasoup.types.Transport, dataProducer: mediasoup.types.DataProducer) => { dataProducer.observer.once('close', () => { + router.appData.dataProducers.delete(dataProducer.id); this.dataProducers.delete(dataProducer.id); logger.info(`Data producer ${dataProducer.id} closed on transport ${transport.id} on router ${router.id}`); }); + router.appData.dataProducers.set(dataProducer.id, dataProducer); this.dataProducers.set(dataProducer.id, dataProducer); logger.info(`Data producer ${dataProducer.id} created on transport ${transport.id} on router ${router.id}`); } - private _addDataConsumer = (router: mediasoup.types.Router, transport: mediasoup.types.Transport, dataConsumer: mediasoup.types.DataConsumer) => { + private _addDataConsumer = (router: mediasoup.types.Router, transport: mediasoup.types.Transport, dataConsumer: mediasoup.types.DataConsumer) => { dataConsumer.observer.once('close', () => { + router.appData.dataConsumers.delete(dataConsumer.id); this.dataConsumers.delete(dataConsumer.id); logger.info(`Data consumer ${dataConsumer.id} closed on transport ${transport.id} on router ${router.id}`); }); + router.appData.dataConsumers.set(dataConsumer.id, dataConsumer); this.dataConsumers.set(dataConsumer.id, dataConsumer); logger.info(`Data consumer ${dataConsumer.id} created on transport ${transport.id} on router ${router.id}`); diff --git a/webapp/.gitignore b/webapp/.gitignore index d6f6d22..fa284f9 100644 --- a/webapp/.gitignore +++ b/webapp/.gitignore @@ -1,4 +1,5 @@ node_modules dist yarn-error.log -build \ No newline at end of file +build +.vscode \ No newline at end of file diff --git a/webapp/src/App.tsx b/webapp/src/App.tsx index c125946..7ef02a4 100644 --- a/webapp/src/App.tsx +++ b/webapp/src/App.tsx @@ -1,35 +1,37 @@ -import { Switch, type Component, Match } from 'solid-js'; +import { Switch, type Component, Match, Show } from 'solid-js'; import { page } from './signals/signals'; import Join from './views/Join'; import { Transition } from 'solid-transition-group'; import ObserverView from './views/OngoingCalls'; import { Grid } from '@suid/material'; -import Menu from './views/Menu'; import ClientMonitor from './components/ClientMonitor/ClientMonitor'; -import PoweredByCmp from './components/PoweredBy/PoweredByCmp'; import VideoCall from './views/VideoCall'; +import Main from './views/Main'; +import { clientStore } from './stores/LocalClientStore'; const App: Component = () => { return ( - - - - + +
- - - + + }> + + + {/* + + */} diff --git a/webapp/src/common/utils.ts b/webapp/src/common/utils.ts index 095fa39..fdd7170 100644 --- a/webapp/src/common/utils.ts +++ b/webapp/src/common/utils.ts @@ -43,4 +43,123 @@ export async function timeoutPromise(promise: Promise, timeout: number): P return result; }); -} \ No newline at end of file +} + +const names: string[] = [ + // Action/Adventure + 'Indiana Jones', + 'James Bond', + 'Ethan Hunt', + 'John McClane', + 'Lara Croft', + 'Jack Sparrow', + 'Han Solo', + 'Neo', + 'Jason Bourne', + + // Sci-Fi/Fantasy + 'Luke Skywalker', + 'Frodo Baggins', + 'Harry Potter', + 'Hermione Granger', + 'Katniss Everdeen', + 'Darth Vader', + 'Gandalf', + 'Optimus Prime', + 'Yoda', + + // Animation + 'Woody', + 'Buzz Lightyear', + 'Elsa', + 'Simba', + 'Mulan', + 'Po (Kung Fu Panda)', + 'Shrek', + 'Nemo', + 'Stitch', + + // Superhero + 'Spider-Man', + 'Iron Man', + 'Batman', + 'Superman', + 'Wonder Woman', + 'Captain America', + 'Hulk', + 'Black Panther', + 'Thor', + + // Horror + 'Michael Myers', + 'Freddy Krueger', + 'Jason Voorhees', + 'Samara Morgan', + 'Regan MacNeil', + 'Jack Torrance', + 'Norman Bates', + 'Carrie White', + 'Pennywise', + + // Drama + 'Forrest Gump', + 'Andy Dufresne', + 'Vito Corleone', + 'Tony Montana', + 'Rocky Balboa', + 'Atticus Finch', + 'Jack Dawson', + 'Rose DeWitt Bukater', + 'Scarlett O\'Hara', + + // Comedy + 'Ace Ventura', + 'Ferris Bueller', + 'Ron Burgundy', + 'Dr. Emmett Brown', + 'Marty McFly', + 'Austin Powers', + 'Napoleon Dynamite', + 'Elle Woods', + 'Alan Garner', + + // Romance + 'Noah Calhoun', + 'Allie Hamilton', + 'Sally Albright', + 'Harry Burns', + 'Edward Lewis', + 'Vivian Ward', + 'Westley', + 'Buttercup', + 'Mr. Darcy', + + // Animated Classics + 'Snow White', + 'Cinderella', + 'Aurora', + 'Ariel', + 'Belle', + 'Aladdin', + 'Jasmine', + 'Pocahontas', + 'Simba', + + // Miscellaneous + 'Maximus Decimus Meridius', + 'William Wallace', + 'John Wick', + 'Tyler Durden', + 'Rick Blaine', + 'Clarice Starling', + 'Marge Gunderson', + 'Amélie Poulain', + 'Alex DeLarge' +]; + + +export function getRandomUserId(): string { + const randomIndex = Math.floor(Math.random() * names.length); + return names[randomIndex]; +} + \ No newline at end of file diff --git a/webapp/src/components/Box.tsx b/webapp/src/components/Box.tsx index 4d5d904..d0e5335 100644 --- a/webapp/src/components/Box.tsx +++ b/webapp/src/components/Box.tsx @@ -1,13 +1,24 @@ -import { Component, JSX } from 'solid-js'; +import { Typography, Popover, SvgIcon } from '@suid/material'; +import { Component, createSignal, JSX, Show } from 'solid-js'; type BoxProps = { title?: string; logo?: string; children?: JSX.Element; full?: boolean; + popoverText?: string, } const Box: Component = (props) => { + const [anchorEl, setAnchorEl] = createSignal(null); + const handlePopoverOpen = (event: { currentTarget: Element }) => { + setAnchorEl(event.currentTarget); + }; + const handlePopoverClose = () => { + setAnchorEl(null); + }; + const open = () => Boolean(anchorEl()); + const divClass = [ 'mt-8 mx-4 sm:mx-auto', props.full ? 'sm:w-full' : 'sm:max-w-sm', @@ -16,7 +27,47 @@ const Box: Component = (props) => { return (
-

{ props.title }

+ + { props.title } + + }> +
+

+ { props.title } +

+ + + + + {props.popoverText} + +
+
{ props.children }
diff --git a/webapp/src/components/Section.tsx b/webapp/src/components/Section.tsx new file mode 100644 index 0000000..7a4303d --- /dev/null +++ b/webapp/src/components/Section.tsx @@ -0,0 +1,36 @@ +import { Component, JSX } from 'solid-js'; + +type SectionProps = { + title?: string; + logo?: string; + children?: JSX.Element; + subsection?: boolean; +} + +const Section: Component = (props) => { + const divClass = [ + 'mt-8 mx-4 sm:mx-auto', + 'max-w-4xl', + // 'sm:w-full', + ].join(' '); + const h1Class = [ + 'text-left', + props.subsection ? 'text-lg' : 'text-3xl', + 'font-semibold', + 'text-gray-700', + 'antialiased', + 'font-sans', + ].join(' '); + // sm:max-w-sm + return ( +
+
+

{ props.title }

+
+ { props.children } +
+
+ ); +}; + +export default Section; diff --git a/webapp/src/signals/signals.tsx b/webapp/src/signals/signals.tsx index 12cae34..99ede37 100644 --- a/webapp/src/signals/signals.tsx +++ b/webapp/src/signals/signals.tsx @@ -9,6 +9,6 @@ export type Page = | 'exit' | 'observer'; -export const [ page, setPage ] = createSignal('lobby'); +export const [ page, setPage ] = createSignal('main'); export type MediaResults = 'tcp' | 'relay' | 'bw' | 'loss' | 'latency' | 'connection'; diff --git a/webapp/src/stores/LocalClientStore.tsx b/webapp/src/stores/LocalClientStore.tsx index 8c41d6a..e8788a4 100644 --- a/webapp/src/stores/LocalClientStore.tsx +++ b/webapp/src/stores/LocalClientStore.tsx @@ -1,5 +1,6 @@ import { createStore } from 'solid-js/store'; import { Connection } from '../utils/Connection'; +import { getRandomUserId } from '../common/utils'; type SavedMediaDevice = { deviceId: string; @@ -28,6 +29,7 @@ export const [clientStore, setClientStore] = createStore({ updateInProgress: false, audioDevices: [], videoDevices: [], + userId: getRandomUserId(), }); export async function updateClientMediaDevices(): Promise { diff --git a/webapp/src/utils/Connection.tsx b/webapp/src/utils/Connection.tsx index 30111c4..9585ed7 100644 --- a/webapp/src/utils/Connection.tsx +++ b/webapp/src/utils/Connection.tsx @@ -32,6 +32,7 @@ export type ConnectionConfig = { monitor: ClientMonitorConfig; callId?: string; forceRelay?: boolean; + userId?: string, } export type ConnectionEventMap = { @@ -107,6 +108,7 @@ export class Connection extends EventEmitter { `${this.config.serverUri}?${[ ['schemaVersion', schemaVersion], ['clientId', this.config.clientId], + ['userId', this.config.userId || ''], ].map(i => `${i[0]}=${i[1]}`).join('&')}` ); this._websocket.onerror = () => reject('Failed to connect to the server'); @@ -370,6 +372,5 @@ export class Connection extends EventEmitter { producer.observer.on('resume', onResume); this.mediaProducers.set(producer.id, producer); } - - + } \ No newline at end of file diff --git a/webapp/src/views/Join.tsx b/webapp/src/views/Join.tsx index f403876..8f76ffc 100644 --- a/webapp/src/views/Join.tsx +++ b/webapp/src/views/Join.tsx @@ -6,8 +6,6 @@ import DeviceSelector from '../components/DeviceSelector'; import LocalClientVideo from '../components/LocalClientVideo'; import { clientStore, setClientStore, updateClientMedia } from '../stores/LocalClientStore'; import { ErrorPaperItem } from '../components/PaperItem'; -import { setPage } from '../signals/signals'; -import Configuration from '../components/Configuration'; import { TextField } from '@suid/material'; import { callConfig, setCallConfig } from '../stores/callConfigStore'; @@ -15,10 +13,12 @@ const Join: Component = () => { const [ error, setError ] = createSignal(); const [ inProgress, setInProgress ] = createSignal(false); - onMount(() => updateClientMedia({})); - + onMount(() => { + updateClientMedia({}); + setCallConfig({ ...callConfig, }); + }); return ( - + {error()} @@ -52,14 +52,15 @@ const Join: Component = () => { setCallConfig({ ...callConfig, callId: e.currentTarget.value })} /> - setClientStore({ ...clientStore, userId: e.currentTarget.value })} - /> - callConfig} /> + /> */} + {/* callConfig} /> */}