/**
* IMPORTANT (PLEASE READ THIS):
*
* This is not the "configuration file" of mediasoup. This is the configuration
* file of the mediasoup-demo app. mediasoup itself is a server-side library, it
* does not read any "configuration file". Instead it exposes an API. This demo
* application just reads settings from this file (once copied to config.js) and
* calls the mediasoup API with those settings when appropriate.
*/const os =require('os');
module.exports ={// Listening hostname (just for `gulp live` task).domain: process.env.DOMAIN||'localhost',// Signaling settings (protoo WebSocket server and HTTP API server).https://{listenIp:'0.0.0.0',// NOTE: Don't change listenPort (client app assumes 4443).listenPort: process.env.PROTOO_LISTEN_PORT||4443,// NOTE: Set your own valid certificate files.tls:{cert: process.env.HTTPS_CERT_FULLCHAIN||`${__dirname}/certs/fullchain.pem`,key: process.env.HTTPS_CERT_PRIVKEY||`${__dirname}/certs/privkey.pem`}},// mediasoup settings.mediasoup:{// Number of mediasoup workers to launch.numWorkers: Object.keys(os.cpus()).length,// mediasoup WorkerSettings.// See https://mediasoup.org/documentation/v3/mediasoup/api/#WorkerSettingsworkerSettings:{logLevel:'warn',logTags:['info','ice','dtls','rtp','srtp','rtcp','rtx','bwe','score','simulcast','svc','sctp'],rtcMinPort: process.env.MEDIASOUP_MIN_PORT||40000,rtcMaxPort: process.env.MEDIASOUP_MAX_PORT||49999},// mediasoup Router options.// See https://mediasoup.org/documentation/v3/mediasoup/api/#RouterOptionsrouterOptions:{mediaCodecs:[{kind:'audio',mimeType:'audio/opus',clockRate:48000,channels:2},{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}}]},// mediasoup WebRtcServer options for WebRTC endpoints (mediasoup-client,// libmediasoupclient).// See https://mediasoup.org/documentation/v3/mediasoup/api/#WebRtcServerOptions// NOTE: mediasoup-demo/server/lib/Room.js will increase this port for// each mediasoup Worker since each Worker is a separate process.webRtcServerOptions:{listenInfos:[{protocol:'udp',ip: process.env.MEDIASOUP_LISTEN_IP||'0.0.0.0',announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP,port:44444},{protocol:'tcp',ip: process.env.MEDIASOUP_LISTEN_IP||'0.0.0.0',announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP,port:44444}],},// mediasoup WebRtcTransport options for WebRTC endpoints (mediasoup-client,// libmediasoupclient).// See https://mediasoup.org/documentation/v3/mediasoup/api/#WebRtcTransportOptionswebRtcTransportOptions:{// listenIps is not needed since webRtcServer is used.// However passing MEDIASOUP_USE_WEBRTC_SERVER=false will change it.listenIps:[{ip: process.env.MEDIASOUP_LISTEN_IP||'0.0.0.0',announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP}],initialAvailableOutgoingBitrate:1000000,minimumAvailableOutgoingBitrate:600000,maxSctpMessageSize:262144,// Additional options that are not part of WebRtcTransportOptions.maxIncomingBitrate:1500000},// mediasoup PlainTransport options for legacy RTP endpoints (FFmpeg,// GStreamer).// See https://mediasoup.org/documentation/v3/mediasoup/api/#PlainTransportOptionsplainTransportOptions:{listenIp:{ip: process.env.MEDIASOUP_LISTEN_IP||'0.0.0.0',announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP},maxSctpMessageSize:262144}}};
/**
* Launch as many mediasoup Workers as given in the configuration file.
*/asyncfunctionrunMediasoupWorkers(){// 从配置文件中获取mediasoup的配置项numWorkers,即需要启动的mediasoup Worker数量。const{ numWorkers }= config.mediasoup;
logger.info('running %d mediasoup Workers...', numWorkers);// 使用mediasoup.createWorker()创建指定数量的mediasoup Worker,并将其存储到全局数组mediasoupWorkers中。for(let i =0; i < numWorkers;++i){const worker =await mediasoup.createWorker({logLevel: config.mediasoup.workerSettings.logLevel,logTags: config.mediasoup.workerSettings.logTags,rtcMinPort:Number(config.mediasoup.workerSettings.rtcMinPort),rtcMaxPort:Number(config.mediasoup.workerSettings.rtcMaxPort)});// 每个mediasoup Worker创建成功后,为其添加一个died事件监听器,当Worker死亡时自动退出进程。// 该事件监听器通过setTimeout()设置了一个2秒的定时器,定时器到期后退出进程。
worker.on('died',()=>{
logger.error('mediasoup Worker died, exiting in 2 seconds... [pid:%d]', worker.pid);setTimeout(()=> process.exit(1),2000);});
mediasoupWorkers.push(worker);// 对于每个mediasoup Worker,如果配置文件中的MEDIASOUP_USE_WEBRTC_SERVER环境变量的值不为false,则在其中创建一个WebRtcServer。// Create a WebRtcServer in this Worker.if(process.env.MEDIASOUP_USE_WEBRTC_SERVER!=='false'){// Each mediasoup Worker will run its own WebRtcServer, so those cannot// share the same listening ports. Hence we increase the value in config.js// for each Worker.// 为了避免多个mediasoup Worker之间端口冲突,配置文件中WebRtcServer的监听端口需要逐个增加,这样每个mediasoup Worker创建的// WebRtcServer监听端口才不会相同。因此,在为每个mediasoup Worker创建WebRtcServer时,会逐个增加WebRtcServer配置中的端口号。const webRtcServerOptions = utils.clone(config.mediasoup.webRtcServerOptions);const portIncrement = mediasoupWorkers.length -1;for(const listenInfo of webRtcServerOptions.listenInfos){
listenInfo.port += portIncrement;}const webRtcServer =await worker.createWebRtcServer(webRtcServerOptions);//每个mediasoup Worker创建的WebRtcServer将被存储到其appData属性中。
worker.appData.webRtcServer = webRtcServer;}// Log worker resource usage every X seconds.// 每个mediasoup Worker将会定时记录其资源使用情况,并使用setInterval()函数设置一个时间间隔,// 即每隔一段时间输出一次mediasoup Worker的资源使用情况。setInterval(async()=>{const usage =await worker.getResourceUsage();
logger.info('mediasoup Worker resource usage [pid:%d]: %o', worker.pid, usage);},120000);}}
runHttpsServer() 函数的主要功能是创建和运行一个基于 HTTPS 的 Web 服务器,并监听配置文件中指定的IP地址和端口。
具体代码和注释如下:
/**
* Create a Node.js HTTPS server. It listens in the IP and port given in the
* configuration file and reuses the Express application as request listener.
*/asyncfunctionrunHttpsServer(){
logger.info('running an HTTPS server...');// HTTPS server for the protoo WebSocket server.// 定义了tls的对象,用于存储HTTPS服务器所需的TLS证书和密钥const tls ={cert: fs.readFileSync(config.https.tls.cert),key: fs.readFileSync(config.https.tls.key)};// 创建一个HTTPS服务器实例,并将tls对象和expressApp作为参数传递进去。
httpsServer = https.createServer(tls, expressApp);// 监听HTTPS服务器的端口和IP地址,并返回一个Promise。awaitnewPromise((resolve)=>{
httpsServer.listen(Number(config.https.listenPort), config.https.listenIp, resolve);});}
/**
* Called from server.js upon a protoo WebSocket connection request from a
* browser.
*
* @param {String} peerId - The id of the protoo peer to be created.
* @param {Boolean} consume - Whether this peer wants to consume from others.
* @param {protoo.WebSocketTransport} protooWebSocketTransport - The associated
* protoo WebSocket transport.
*/handleProtooConnection({ peerId, consume, protooWebSocketTransport }){// 为了确保每个连接都是唯一的,根据peerId查找是否已经存在同名的protoo Peer对象,如果存在,则关闭该protoo Peer对象。const existingPeer =this._protooRoom.getPeer(peerId);if(existingPeer){
logger.warn('handleProtooConnection() | there is already a protoo Peer with same peerId, closing it [peerId:%s]',
peerId);
existingPeer.close();}// 如果不存在同名的protoo Peer对象,则创建一个新的protoo Peer对象。// 并通过peer.data对象存储相关数据,如是否已经加入房间、用户名称等等。// 同时,也创建了几个map对象,用于存储房间中的生产者、消费者、数据生产者和数据消费者对象。let peer;// Create a new protoo Peer with the given peerId.try{
peer =this._protooRoom.createPeer(peerId, protooWebSocketTransport);}catch(error){
logger.error('protooRoom.createPeer() failed:%o', error);}// Use the peer.data object to store mediasoup related objects.// Not joined after a custom protoo 'join' request is later received.
peer.data.consume = consume;
peer.data.joined =false;
peer.data.displayName =undefined;
peer.data.device =undefined;
peer.data.rtpCapabilities =undefined;
peer.data.sctpCapabilities =undefined;// Have mediasoup related maps ready even before the Peer joins since we// allow creating Transports before joining.
peer.data.transports =newMap();
peer.data.producers =newMap();
peer.data.consumers =newMap();
peer.data.dataProducers =newMap();
peer.data.dataConsumers =newMap();// 监听protoo Peer对象的request事件,当有请求到达时,执行_handleProtooRequest函数来处理请求,并返回相应的结果。// 如果出现异常,则通过reject函数返回错误信息。
peer.on('request',(request, accept, reject)=>{
logger.debug('protoo Peer "request" event [method:%s, peerId:%s]',
request.method, peer.id);this._handleProtooRequest(peer, request, accept, reject).catch((error)=>{
logger.error('request failed:%o', error);reject(error);});});// 当一个protoo连接关闭时,触发protoo Peer对象的close事件。
peer.on('close',()=>{// 首先判断房间是否已经关闭,如果已经关闭则直接返回,避免重复关闭。if(this._closed)return;
logger.debug('protoo Peer "close" event [peerId:%s]', peer.id);// If the Peer was joined, notify all Peers.// 如果该Peer已经加入房间,通知所有其他Peers该Peer已经关闭。if(peer.data.joined){for(const otherPeer ofthis._getJoinedPeers({excludePeer: peer })){
otherPeer.notify('peerClosed',{peerId: peer.id }).catch(()=>{});}}// Iterate and close all mediasoup Transport associated to this Peer, so all// its Producers and Consumers will also be closed.// 遍历所有该Peer创建的Transport并关闭,这样该Peer创建的所有Producers和Consumers都将被关闭。for(const transport of peer.data.transports.values()){
transport.close();}// If this is the latest Peer in the room, close the room.// 判断该Peer是否是房间中的最后一个Peer,如果是,则关闭整个房间。if(this._protooRoom.peers.length ===0){
logger.info('last Peer in the room left, closing the room [roomId:%s]',this._roomId);this.close();}});}