Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ef92ca7
implement mediasoup server
obrucheoghene Aug 13, 2025
1e450fa
implement peer service
obrucheoghene Aug 13, 2025
94b1b4f
update config
obrucheoghene Aug 13, 2025
cec94fa
update interface
obrucheoghene Aug 13, 2025
3c182d7
add mediasoup router settings, transport and webrtc options
obrucheoghene Aug 14, 2025
185a4dc
add serverid in config
obrucheoghene Aug 14, 2025
2a93292
implement getkey to get redis key
obrucheoghene Aug 14, 2025
cd63f69
add transport connection params
obrucheoghene Aug 14, 2025
cfda33f
register and unregister medianode
obrucheoghene Aug 16, 2025
731c2ec
create webrtc server
obrucheoghene Aug 16, 2025
764723c
rename file
obrucheoghene Aug 16, 2025
989301c
update type and helper file import path
obrucheoghene Aug 16, 2025
6a04695
regenerate proto type files
obrucheoghene Aug 17, 2025
0de975d
optimise grpc server
obrucheoghene Aug 17, 2025
83031ea
update proto configuration
obrucheoghene Aug 17, 2025
78b5dfd
rename serverid to nodeid
obrucheoghene Aug 17, 2025
678a95a
use node id instead
obrucheoghene Aug 17, 2025
cd85682
implement grpc-server optimization
obrucheoghene Aug 17, 2025
d36be74
rename pubsubevents to pubsubactions
obrucheoghene Aug 17, 2025
ec6da1f
change get key to get redis key
obrucheoghene Aug 17, 2025
fb74057
change name
obrucheoghene Aug 17, 2025
3cd82ec
implement signalnode
obrucheoghene Aug 17, 2025
73be983
change events to actions
obrucheoghene Aug 17, 2025
cb5d4d6
add actions
obrucheoghene Aug 17, 2025
2c1e2fb
change nodeid to client id
obrucheoghene Aug 17, 2025
121efb0
fix server node error
obrucheoghene Aug 17, 2025
5cdae42
fix error in grpc server and signal node
obrucheoghene Aug 19, 2025
3eaa026
remove error from room
obrucheoghene Aug 19, 2025
ddfef31
implement immediate response with grpc
obrucheoghene Aug 20, 2025
e239c9e
fix error in room
obrucheoghene Aug 21, 2025
f430ec3
fix build error
obrucheoghene Aug 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
386 changes: 383 additions & 3 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"express": "^5.1.0",
"helmet": "^8.1.0",
"mediasoup": "^3.18.0",
"public-ip": "^7.0.1",
"redis": "^5.8.0",
"zod": "^4.0.17"
}
Expand Down
2 changes: 1 addition & 1 deletion proto-gen.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash

npx proto-loader-gen-types --grpcLib=@grpc/grpc-js --outDir=src/protos/ src/protos/*.proto
npx proto-loader-gen-types --grpcLib=@grpc/grpc-js --outDir=src/protos/gen/ src/protos/*.proto
24 changes: 17 additions & 7 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import fs from 'fs';
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
Expand All @@ -8,19 +7,19 @@ import config from './config';
import { Routes } from './routes';
import { redisServer } from './servers/redis-server';
import { grpcServer } from './servers/grpc-server';

const serverOption = {
key: fs.readFileSync(config.tls.key, 'utf8'),
cert: fs.readFileSync(config.tls.cert, 'utf8'),
};
import { MediaNodeData } from './types';
import { getRedisKey, registerMediaNode } from './lib/utils';
import { mediaSoupServer } from './servers/mediasoup-server';

const app = express();
app.use(cors(config.cors));
app.use(helmet());
app.use(express.json());
app.use('/', Routes);

const httpsServer = createServer(serverOption, app);
const httpsServer = createServer(config.httpsServerOptions, app);

let medianodeData: MediaNodeData;

(async (): Promise<void> => {
try {
Expand All @@ -29,6 +28,11 @@ const httpsServer = createServer(serverOption, app);
console.log(`Server running on port ${config.port}`);
});
await grpcServer.start();

await mediaSoupServer.start();

medianodeData = await registerMediaNode();
console.log('Register medianode');
} catch (error) {
console.error('Initialization error:', error);
process.exit(1);
Expand All @@ -37,6 +41,12 @@ const httpsServer = createServer(serverOption, app);

const shutdown = async (): Promise<void> => {
try {
await redisServer.sRem(
getRedisKey['medianodesRunning'](),
JSON.stringify(medianodeData)
);
console.log('Delete medianode');

await redisServer.disconnect();
httpsServer.close();
console.log('Application shut down gracefully');
Expand Down
122 changes: 117 additions & 5 deletions src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,138 @@
import fs from 'fs';
import os from 'os';
import path from 'path';

import * as dotenv from 'dotenv';
import { types as mediasoupTypes } from 'mediasoup';

dotenv.config();

const certPath = path.join(__dirname, '..', 'certs', 'fullchain.pem');
const keyPath = path.join(__dirname, '..', 'certs', 'privkey.pem');
const certFile =
process.env.HTTPS_CERT ||
path.join(__dirname, '..', 'certs', 'fullchain.pem');
const keyFile =
process.env.HTTPS_KEY || path.join(__dirname, '..', 'certs', 'privkey.pem');

const LISTEN_IP = process.env.LISTEN_IP || '0.0.0.0';
const ANNOUNCED_ADDRESS = process.env.ANNOUNCED_ADDRESS || '127.0.0.1';

const config = {
nodeId: `mnode-1`,
env: process.env.NODE_ENV,
cors: {
origin: process.env.NODE_ENV === 'production' ? ['https://mitsi.app'] : '*',
methods: ['GET', 'POST'],
},
tls: {
cert: process.env.HTTPS_CERT || certPath,
key: process.env.HTTPS_KEY || keyPath,
httpsServerOptions: {
key: fs.readFileSync(keyFile, 'utf8'),
cert: fs.readFileSync(certFile, 'utf8'),
},
port: process.env.PORT || 4000,
grpcPort: process.env.GRPC_PORT || 50052,
cpus: Object.keys(os.cpus()).length,

apiServerUrl: process.env.API_SERVER_URL,
apiServerApiKey: process.env.API_SERVER_API_KEY,
recordingServerUrl: process.env.RECORDING_SERVER_URL,
redisServerUrl: process.env.REDIS_SERVER_URL || 'redis://localhost:6379',

mediasoup: {
maxWorkerLoad: parseInt(process.env.MAX_WORKER_LOAD || '100'),
workerSettings: {
dtlsCertificateFile: certFile,
dtlsPrivateKeyFile: keyFile,
rtcMinPort: parseInt(process.env.RTC_MIN_PORT || '2000'),
rtcMaxPort: parseInt(process.env.RTC_MAX_PORT || '2300'),
logLevel: 'warn' as mediasoupTypes.WorkerLogLevel,
logTags: [
'info',
'ice',
'dtls',
'rtp',
'srtp',
'rtcp',
'rtx',
'bwe',
'score',
'simulcast',
'svc',
'sctp',
] as Array<mediasoupTypes.WorkerLogTag>,
},
webRtcServer: {
listenInfos: [
{
protocol: 'udp',
ip: LISTEN_IP,
announcedAddress: ANNOUNCED_ADDRESS,
},
{
protocol: 'tcp',
ip: LISTEN_IP,
announcedAddress: ANNOUNCED_ADDRESS,
},
] as Array<mediasoupTypes.TransportListenInfo>,
},
routerMediaCodecs: [
{
kind: 'audio',
mimeType: 'audio/opus',
clockRate: 48000,
channels: 2,
// parameters: {
// 'stereo': 1,
// 'sprop-stereo': 1,
// 'maxplaybackrate': 48000,
// 'useinbandfec': 1
// }
},
{
kind: 'video',
mimeType: 'video/VP8',
clockRate: 90000,
parameters: {
'x-google-start-bitrate': 1000,
},
},
// {
// kind: 'video',
// mimeType: 'video/VP9',
// clockRate: 90000,
// parameters: {
// 'profile-id': 2,
// 'x-google-start-bitrate': 1000
// }
// },
// {
// kind: 'video',
// mimeType: 'video/h264',
// clockRate: 90000,
// parameters: {
// 'packetization-mode': 1,
// 'profile-level-id': '4d0032',
// 'level-asymmetry-allowed': 1,
// 'x-google-start-bitrate': 1000
// }
// },
// {
// kind: 'video',
// mimeType: 'video/h264',
// clockRate: 90000,
// parameters: {
// 'packetization-mode': 1,
// 'profile-level-id': '42e01f',
// 'level-asymmetry-allowed': 1,
// 'x-google-start-bitrate': 1000
// }
// }
] as Array<mediasoupTypes.RtpCodecCapability>,

transportListenInfo: {
protocol: 'udp',
ip: LISTEN_IP,
announcedAddress: ANNOUNCED_ADDRESS,
} as mediasoupTypes.TransportListenInfo,
},
};

export default config;
37 changes: 37 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import config from '../config';
import { redisServer } from '../servers/redis-server';
import { MediaNodeData } from '../types';

export const getRedisKey = {
room: (roomId: string): string => `room-${roomId}`,
lobby: (roomId: string): string => `lobby-${roomId}`,
roomPeers: (roomId: string): string => `room-${roomId}-peers`,
roomPeerIds: (roomId: string): string => `room-${roomId}-peerids`,
roomActiveSpeakerPeerId: (roomId: string): string =>
`room-${roomId}-activespeakerpeerid`,
roomsOngoing: (): string => `rooms-ongoing`,
medianodesRunning: (): string => `medianodes-running`,
signalnodesRunning: (): string => `signalnodes-running`,
roomMedianodes: (roomId: string): string => `room-${roomId}-medianodes`,
roomSignalnodes: (roomId: string): string => `room-${roomId}-signalnodes`,
};

export const registerMediaNode = async (): Promise<MediaNodeData> => {
try {
// const { publicIpv4 } = await import('public-ip');
// const ip = await publicIpv4();
const medianodeData: MediaNodeData = {
id: config.nodeId,
ip: '0.0.0.0',
address: `${config.port}`,
grpcPort: `${config.grpcPort}`,
};
await redisServer.sAdd(
getRedisKey['medianodesRunning'](),
JSON.stringify(medianodeData)
);
return medianodeData;
} catch (error) {
throw error;
}
};
19 changes: 19 additions & 0 deletions src/protos/gen/media-signaling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type * as grpc from '@grpc/grpc-js';
import type { MessageTypeDefinition } from '@grpc/proto-loader';

import type { MediaSignalingClient as _mediaSignalingPackage_MediaSignalingClient, MediaSignalingDefinition as _mediaSignalingPackage_MediaSignalingDefinition } from './mediaSignalingPackage/MediaSignaling';
import type { MessageRequest as _mediaSignalingPackage_MessageRequest, MessageRequest__Output as _mediaSignalingPackage_MessageRequest__Output } from './mediaSignalingPackage/MessageRequest';
import type { MessageResponse as _mediaSignalingPackage_MessageResponse, MessageResponse__Output as _mediaSignalingPackage_MessageResponse__Output } from './mediaSignalingPackage/MessageResponse';

type SubtypeConstructor<Constructor extends new (...args: any) => any, Subtype> = {
new(...args: ConstructorParameters<Constructor>): Subtype;
};

export interface ProtoGrpcType {
mediaSignalingPackage: {
MediaSignaling: SubtypeConstructor<typeof grpc.Client, _mediaSignalingPackage_MediaSignalingClient> & { service: _mediaSignalingPackage_MediaSignalingDefinition }
MessageRequest: MessageTypeDefinition<_mediaSignalingPackage_MessageRequest, _mediaSignalingPackage_MessageRequest__Output>
MessageResponse: MessageTypeDefinition<_mediaSignalingPackage_MessageResponse, _mediaSignalingPackage_MessageResponse__Output>
}
}

23 changes: 23 additions & 0 deletions src/protos/gen/mediaSignalingPackage/MediaSignaling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Original file: src/protos/media-signaling.proto

import type * as grpc from '@grpc/grpc-js'
import type { MethodDefinition } from '@grpc/proto-loader'
import type { MessageRequest as _mediaSignalingPackage_MessageRequest, MessageRequest__Output as _mediaSignalingPackage_MessageRequest__Output } from '../mediaSignalingPackage/MessageRequest';
import type { MessageResponse as _mediaSignalingPackage_MessageResponse, MessageResponse__Output as _mediaSignalingPackage_MessageResponse__Output } from '../mediaSignalingPackage/MessageResponse';

export interface MediaSignalingClient extends grpc.Client {
Message(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_mediaSignalingPackage_MessageRequest, _mediaSignalingPackage_MessageResponse__Output>;
Message(options?: grpc.CallOptions): grpc.ClientDuplexStream<_mediaSignalingPackage_MessageRequest, _mediaSignalingPackage_MessageResponse__Output>;
message(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_mediaSignalingPackage_MessageRequest, _mediaSignalingPackage_MessageResponse__Output>;
message(options?: grpc.CallOptions): grpc.ClientDuplexStream<_mediaSignalingPackage_MessageRequest, _mediaSignalingPackage_MessageResponse__Output>;

}

export interface MediaSignalingHandlers extends grpc.UntypedServiceImplementation {
Message: grpc.handleBidiStreamingCall<_mediaSignalingPackage_MessageRequest__Output, _mediaSignalingPackage_MessageResponse>;

}

export interface MediaSignalingDefinition extends grpc.ServiceDefinition {
Message: MethodDefinition<_mediaSignalingPackage_MessageRequest, _mediaSignalingPackage_MessageResponse, _mediaSignalingPackage_MessageRequest__Output, _mediaSignalingPackage_MessageResponse__Output>
}
14 changes: 14 additions & 0 deletions src/protos/gen/mediaSignalingPackage/MessageRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Original file: src/protos/media-signaling.proto


export interface MessageRequest {
'action'?: (string);
'args'?: (string);
'requestId'?: (string);
}

export interface MessageRequest__Output {
'action'?: (string);
'args'?: (string);
'requestId'?: (string);
}
14 changes: 14 additions & 0 deletions src/protos/gen/mediaSignalingPackage/MessageResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Original file: src/protos/media-signaling.proto


export interface MessageResponse {
'action'?: (string);
'args'?: (string);
'requestId'?: (string);
}

export interface MessageResponse__Output {
'action'?: (string);
'args'?: (string);
'requestId'?: (string);
}
21 changes: 12 additions & 9 deletions src/protos/media-signaling.proto
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
syntax = "proto3";
package media_signaling_package;

package mediaSignalingPackage;

service MediaSignaling {
rpc SendMessage(stream SendMessageRequest) returns (stream SendMessageResponse) {};
}
message SendMessageRequest {
string type = 1;
map<string, string> args = 2;
rpc Message(stream MessageRequest) returns (stream MessageResponse) {};
}

message SendMessageResponse {
string type = 1;
map<string, string> args = 2;
message MessageRequest {
string action = 1;
string args = 2;
string requestId = 3;
}

message MessageResponse {
string action = 1;
string args = 2;
string requestId = 3;
}
19 changes: 0 additions & 19 deletions src/protos/media-signaling.ts

This file was deleted.

23 changes: 0 additions & 23 deletions src/protos/media_signaling_package/MediaSignaling.ts

This file was deleted.

Loading