[*] 结构调整
This commit is contained in:
@@ -16,5 +16,5 @@ git submodule update --remote
|
||||
## 使用
|
||||
|
||||
```
|
||||
|
||||
sudo npm install
|
||||
```
|
||||
164
taoyao-media/config.js
Normal file
164
taoyao-media/config.js
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* 配置
|
||||
*/
|
||||
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/#WorkerSettings
|
||||
workerSettings :
|
||||
{
|
||||
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/#RouterOptions
|
||||
routerOptions :
|
||||
{
|
||||
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/#WebRtcTransportOptions
|
||||
webRtcTransportOptions :
|
||||
{
|
||||
// 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/#PlainTransportOptions
|
||||
plainTransportOptions :
|
||||
{
|
||||
listenIp :
|
||||
{
|
||||
ip : process.env.MEDIASOUP_LISTEN_IP || '0.0.0.0',
|
||||
announcedIp : process.env.MEDIASOUP_ANNOUNCED_IP
|
||||
},
|
||||
maxSctpMessageSize : 262144
|
||||
}
|
||||
}
|
||||
};
|
||||
16
taoyao-media/package.json
Normal file
16
taoyao-media/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "taoyao-media",
|
||||
"author": "acgist",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"license": "All Rights Reserved",
|
||||
"description": "taoyao media server",
|
||||
"scripts": {
|
||||
"start": "DEBUG=${DEBUG:='*mediasoup* *INFO* *WARN* *ERROR*'} INTERACTIVE=${INTERACTIVE:='true'} node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"mediasoup": "file:./mediasoup"
|
||||
},
|
||||
"devDependencies": {
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,557 @@
|
||||
/**
|
||||
* 媒体服务
|
||||
*/
|
||||
#!/usr/bin/env node
|
||||
|
||||
process.title = 'taoyao-client';
|
||||
process.env.DEBUG = process.env.DEBUG || '*INFO* *WARN* *ERROR*';
|
||||
|
||||
const config = require('./config');
|
||||
|
||||
/* eslint-disable no-console */
|
||||
console.log('process.env.DEBUG:', process.env.DEBUG);
|
||||
console.log('config.js:\n%s', JSON.stringify(config, null, ' '));
|
||||
/* eslint-enable no-console */
|
||||
|
||||
const fs = require('fs');
|
||||
const url = require('url');
|
||||
const https = require('https');
|
||||
const protoo = require('protoo-server');
|
||||
const express = require('express');
|
||||
const mediasoup = require('mediasoup');
|
||||
|
||||
const logger = new Logger();
|
||||
|
||||
// Async queue to manage rooms.
|
||||
// @type {AwaitQueue}
|
||||
const queue = new AwaitQueue();
|
||||
|
||||
// Map of Room instances indexed by roomId.
|
||||
// @type {Map<Number, Room>}
|
||||
const rooms = new Map();
|
||||
|
||||
// HTTPS server.
|
||||
// @type {https.Server}
|
||||
let httpsServer;
|
||||
|
||||
// Express application.
|
||||
// @type {Function}
|
||||
let expressApp;
|
||||
|
||||
// Protoo WebSocket server.
|
||||
// @type {protoo.WebSocketServer}
|
||||
let protooWebSocketServer;
|
||||
|
||||
// mediasoup Workers.
|
||||
// @type {Array<mediasoup.Worker>}
|
||||
const mediasoupWorkers = [];
|
||||
|
||||
// Index of next mediasoup Worker to use.
|
||||
// @type {Number}
|
||||
let nextMediasoupWorkerIdx = 0;
|
||||
|
||||
run();
|
||||
|
||||
async function run()
|
||||
{
|
||||
// Open the interactive server.
|
||||
await interactiveServer();
|
||||
|
||||
// Open the interactive client.
|
||||
if (process.env.INTERACTIVE === 'true' || process.env.INTERACTIVE === '1')
|
||||
await interactiveClient();
|
||||
|
||||
// Run a mediasoup Worker.
|
||||
await runMediasoupWorkers();
|
||||
|
||||
// Create Express app.
|
||||
await createExpressApp();
|
||||
|
||||
// Run HTTPS server.
|
||||
await runHttpsServer();
|
||||
|
||||
// Run a protoo WebSocketServer.
|
||||
await runProtooWebSocketServer();
|
||||
|
||||
// Log rooms status every X seconds.
|
||||
setInterval(() =>
|
||||
{
|
||||
for (const room of rooms.values())
|
||||
{
|
||||
room.logStatus();
|
||||
}
|
||||
}, 120000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch as many mediasoup Workers as given in the configuration file.
|
||||
*/
|
||||
async function runMediasoupWorkers()
|
||||
{
|
||||
const { numWorkers } = config.mediasoup;
|
||||
|
||||
logger.info('running %d mediasoup Workers...', numWorkers);
|
||||
|
||||
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)
|
||||
});
|
||||
|
||||
worker.on('died', () =>
|
||||
{
|
||||
logger.error(
|
||||
'mediasoup Worker died, exiting in 2 seconds... [pid:%d]', worker.pid);
|
||||
|
||||
setTimeout(() => process.exit(1), 2000);
|
||||
});
|
||||
|
||||
mediasoupWorkers.push(worker);
|
||||
|
||||
// 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.
|
||||
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);
|
||||
|
||||
worker.appData.webRtcServer = webRtcServer;
|
||||
}
|
||||
|
||||
// Log worker resource usage every X seconds.
|
||||
setInterval(async () =>
|
||||
{
|
||||
const usage = await worker.getResourceUsage();
|
||||
|
||||
logger.info('mediasoup Worker resource usage [pid:%d]: %o', worker.pid, usage);
|
||||
}, 120000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Express based API server to manage Broadcaster requests.
|
||||
*/
|
||||
async function createExpressApp()
|
||||
{
|
||||
logger.info('creating Express app...');
|
||||
|
||||
expressApp = express();
|
||||
|
||||
expressApp.use(bodyParser.json());
|
||||
|
||||
/**
|
||||
* For every API request, verify that the roomId in the path matches and
|
||||
* existing room.
|
||||
*/
|
||||
expressApp.param(
|
||||
'roomId', (req, res, next, roomId) =>
|
||||
{
|
||||
// The room must exist for all API requests.
|
||||
if (!rooms.has(roomId))
|
||||
{
|
||||
const error = new Error(`room with id "${roomId}" not found`);
|
||||
|
||||
error.status = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
req.room = rooms.get(roomId);
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
/**
|
||||
* API GET resource that returns the mediasoup Router RTP capabilities of
|
||||
* the room.
|
||||
*/
|
||||
expressApp.get(
|
||||
'/rooms/:roomId', (req, res) =>
|
||||
{
|
||||
const data = req.room.getRouterRtpCapabilities();
|
||||
|
||||
res.status(200).json(data);
|
||||
});
|
||||
|
||||
/**
|
||||
* POST API to create a Broadcaster.
|
||||
*/
|
||||
expressApp.post(
|
||||
'/rooms/:roomId/broadcasters', async (req, res, next) =>
|
||||
{
|
||||
const {
|
||||
id,
|
||||
displayName,
|
||||
device,
|
||||
rtpCapabilities
|
||||
} = req.body;
|
||||
|
||||
try
|
||||
{
|
||||
const data = await req.room.createBroadcaster(
|
||||
{
|
||||
id,
|
||||
displayName,
|
||||
device,
|
||||
rtpCapabilities
|
||||
});
|
||||
|
||||
res.status(200).json(data);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE API to delete a Broadcaster.
|
||||
*/
|
||||
expressApp.delete(
|
||||
'/rooms/:roomId/broadcasters/:broadcasterId', (req, res) =>
|
||||
{
|
||||
const { broadcasterId } = req.params;
|
||||
|
||||
req.room.deleteBroadcaster({ broadcasterId });
|
||||
|
||||
res.status(200).send('broadcaster deleted');
|
||||
});
|
||||
|
||||
/**
|
||||
* POST API to create a mediasoup Transport associated to a Broadcaster.
|
||||
* It can be a PlainTransport or a WebRtcTransport depending on the
|
||||
* type parameters in the body. There are also additional parameters for
|
||||
* PlainTransport.
|
||||
*/
|
||||
expressApp.post(
|
||||
'/rooms/:roomId/broadcasters/:broadcasterId/transports',
|
||||
async (req, res, next) =>
|
||||
{
|
||||
const { broadcasterId } = req.params;
|
||||
const { type, rtcpMux, comedia, sctpCapabilities } = req.body;
|
||||
|
||||
try
|
||||
{
|
||||
const data = await req.room.createBroadcasterTransport(
|
||||
{
|
||||
broadcasterId,
|
||||
type,
|
||||
rtcpMux,
|
||||
comedia,
|
||||
sctpCapabilities
|
||||
});
|
||||
|
||||
res.status(200).json(data);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST API to connect a Transport belonging to a Broadcaster. Not needed
|
||||
* for PlainTransport if it was created with comedia option set to true.
|
||||
*/
|
||||
expressApp.post(
|
||||
'/rooms/:roomId/broadcasters/:broadcasterId/transports/:transportId/connect',
|
||||
async (req, res, next) =>
|
||||
{
|
||||
const { broadcasterId, transportId } = req.params;
|
||||
const { dtlsParameters } = req.body;
|
||||
|
||||
try
|
||||
{
|
||||
const data = await req.room.connectBroadcasterTransport(
|
||||
{
|
||||
broadcasterId,
|
||||
transportId,
|
||||
dtlsParameters
|
||||
});
|
||||
|
||||
res.status(200).json(data);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST API to create a mediasoup Producer associated to a Broadcaster.
|
||||
* The exact Transport in which the Producer must be created is signaled in
|
||||
* the URL path. Body parameters include kind and rtpParameters of the
|
||||
* Producer.
|
||||
*/
|
||||
expressApp.post(
|
||||
'/rooms/:roomId/broadcasters/:broadcasterId/transports/:transportId/producers',
|
||||
async (req, res, next) =>
|
||||
{
|
||||
const { broadcasterId, transportId } = req.params;
|
||||
const { kind, rtpParameters } = req.body;
|
||||
|
||||
try
|
||||
{
|
||||
const data = await req.room.createBroadcasterProducer(
|
||||
{
|
||||
broadcasterId,
|
||||
transportId,
|
||||
kind,
|
||||
rtpParameters
|
||||
});
|
||||
|
||||
res.status(200).json(data);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST API to create a mediasoup Consumer associated to a Broadcaster.
|
||||
* The exact Transport in which the Consumer must be created is signaled in
|
||||
* the URL path. Query parameters must include the desired producerId to
|
||||
* consume.
|
||||
*/
|
||||
expressApp.post(
|
||||
'/rooms/:roomId/broadcasters/:broadcasterId/transports/:transportId/consume',
|
||||
async (req, res, next) =>
|
||||
{
|
||||
const { broadcasterId, transportId } = req.params;
|
||||
const { producerId } = req.query;
|
||||
|
||||
try
|
||||
{
|
||||
const data = await req.room.createBroadcasterConsumer(
|
||||
{
|
||||
broadcasterId,
|
||||
transportId,
|
||||
producerId
|
||||
});
|
||||
|
||||
res.status(200).json(data);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST API to create a mediasoup DataConsumer associated to a Broadcaster.
|
||||
* The exact Transport in which the DataConsumer must be created is signaled in
|
||||
* the URL path. Query body must include the desired producerId to
|
||||
* consume.
|
||||
*/
|
||||
expressApp.post(
|
||||
'/rooms/:roomId/broadcasters/:broadcasterId/transports/:transportId/consume/data',
|
||||
async (req, res, next) =>
|
||||
{
|
||||
const { broadcasterId, transportId } = req.params;
|
||||
const { dataProducerId } = req.body;
|
||||
|
||||
try
|
||||
{
|
||||
const data = await req.room.createBroadcasterDataConsumer(
|
||||
{
|
||||
broadcasterId,
|
||||
transportId,
|
||||
dataProducerId
|
||||
});
|
||||
|
||||
res.status(200).json(data);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST API to create a mediasoup DataProducer associated to a Broadcaster.
|
||||
* The exact Transport in which the DataProducer must be created is signaled in
|
||||
*/
|
||||
expressApp.post(
|
||||
'/rooms/:roomId/broadcasters/:broadcasterId/transports/:transportId/produce/data',
|
||||
async (req, res, next) =>
|
||||
{
|
||||
const { broadcasterId, transportId } = req.params;
|
||||
const { label, protocol, sctpStreamParameters, appData } = req.body;
|
||||
|
||||
try
|
||||
{
|
||||
const data = await req.room.createBroadcasterDataProducer(
|
||||
{
|
||||
broadcasterId,
|
||||
transportId,
|
||||
label,
|
||||
protocol,
|
||||
sctpStreamParameters,
|
||||
appData
|
||||
});
|
||||
|
||||
res.status(200).json(data);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Error handler.
|
||||
*/
|
||||
expressApp.use(
|
||||
(error, req, res, next) =>
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
logger.warn('Express app %s', String(error));
|
||||
|
||||
error.status = error.status || (error.name === 'TypeError' ? 400 : 500);
|
||||
|
||||
res.statusMessage = error.message;
|
||||
res.status(error.status).send(String(error));
|
||||
}
|
||||
else
|
||||
{
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
async function runHttpsServer()
|
||||
{
|
||||
logger.info('running an HTTPS server...');
|
||||
|
||||
// HTTPS server for the protoo WebSocket server.
|
||||
const tls =
|
||||
{
|
||||
cert : fs.readFileSync(config.https.tls.cert),
|
||||
key : fs.readFileSync(config.https.tls.key)
|
||||
};
|
||||
|
||||
httpsServer = https.createServer(tls, expressApp);
|
||||
|
||||
await new Promise((resolve) =>
|
||||
{
|
||||
httpsServer.listen(
|
||||
Number(config.https.listenPort), config.https.listenIp, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a protoo WebSocketServer to allow WebSocket connections from browsers.
|
||||
*/
|
||||
async function runProtooWebSocketServer()
|
||||
{
|
||||
logger.info('running protoo WebSocketServer...');
|
||||
|
||||
// Create the protoo WebSocket server.
|
||||
protooWebSocketServer = new protoo.WebSocketServer(httpsServer,
|
||||
{
|
||||
maxReceivedFrameSize : 960000, // 960 KBytes.
|
||||
maxReceivedMessageSize : 960000,
|
||||
fragmentOutgoingMessages : true,
|
||||
fragmentationThreshold : 960000
|
||||
});
|
||||
|
||||
// Handle connections from clients.
|
||||
protooWebSocketServer.on('connectionrequest', (info, accept, reject) =>
|
||||
{
|
||||
// The client indicates the roomId and peerId in the URL query.
|
||||
const u = url.parse(info.request.url, true);
|
||||
const roomId = u.query['roomId'];
|
||||
const peerId = u.query['peerId'];
|
||||
|
||||
if (!roomId || !peerId)
|
||||
{
|
||||
reject(400, 'Connection request without roomId and/or peerId');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let consumerReplicas = Number(u.query['consumerReplicas']);
|
||||
|
||||
if (isNaN(consumerReplicas))
|
||||
{
|
||||
consumerReplicas = 0;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
'protoo connection request [roomId:%s, peerId:%s, address:%s, origin:%s]',
|
||||
roomId, peerId, info.socket.remoteAddress, info.origin);
|
||||
|
||||
// Serialize this code into the queue to avoid that two peers connecting at
|
||||
// the same time with the same roomId create two separate rooms with same
|
||||
// roomId.
|
||||
queue.push(async () =>
|
||||
{
|
||||
const room = await getOrCreateRoom({ roomId, consumerReplicas });
|
||||
|
||||
// Accept the protoo WebSocket connection.
|
||||
const protooWebSocketTransport = accept();
|
||||
|
||||
room.handleProtooConnection({ peerId, protooWebSocketTransport });
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('room creation or room joining failed:%o', error);
|
||||
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next mediasoup Worker.
|
||||
*/
|
||||
function getMediasoupWorker()
|
||||
{
|
||||
const worker = mediasoupWorkers[nextMediasoupWorkerIdx];
|
||||
|
||||
if (++nextMediasoupWorkerIdx === mediasoupWorkers.length)
|
||||
nextMediasoupWorkerIdx = 0;
|
||||
|
||||
return worker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Room instance (or create one if it does not exist).
|
||||
*/
|
||||
async function getOrCreateRoom({ roomId, consumerReplicas })
|
||||
{
|
||||
let room = rooms.get(roomId);
|
||||
|
||||
// If the Room does not exist create a new one.
|
||||
if (!room)
|
||||
{
|
||||
logger.info('creating a new Room [roomId:%s]', roomId);
|
||||
|
||||
const mediasoupWorker = getMediasoupWorker();
|
||||
|
||||
room = await Room.create({ mediasoupWorker, roomId, consumerReplicas });
|
||||
|
||||
rooms.set(roomId, room);
|
||||
room.on('close', () => rooms.delete(roomId));
|
||||
}
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
@@ -7,13 +7,17 @@
|
||||
|taoyao|桃夭|桃之夭夭灼灼其华|
|
||||
|taoyao-boot|基础|基础模块|
|
||||
|taoyao-node|集群|集群模块|
|
||||
|taoyao-live|直播|直播、连麦、监控、视频同看|
|
||||
|taoyao-media|媒体|Mediasoup|
|
||||
|taoyao-media|媒体|媒体模块|
|
||||
|taoyao-signal|信令|信令服务|
|
||||
|taoyao-server|服务|启动服务|
|
||||
|taoyao-meeting|会议|会议模式、广播模式、单人对讲|
|
||||
|
||||
> 媒体处理均由Mediasoup实现
|
||||
### 直播
|
||||
|
||||
直播、连麦、监控、视频同看
|
||||
|
||||
### 会议
|
||||
|
||||
会议模式、广播模式、单人对讲
|
||||
|
||||
## 模块关系
|
||||
|
||||
@@ -21,8 +25,6 @@
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| taoyao-server |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| taoyao-live | taoyao-meeting |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| taoyao-media | Mediasoup |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| taoyao-signal |
|
||||
|
||||
@@ -37,11 +37,9 @@
|
||||
<modules>
|
||||
<module>taoyao-boot</module>
|
||||
<module>taoyao-node</module>
|
||||
<module>taoyao-live</module>
|
||||
<module>taoyao-media</module>
|
||||
<module>taoyao-signal</module>
|
||||
<module>taoyao-server</module>
|
||||
<module>taoyao-meeting</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
@@ -109,7 +107,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.acgist</groupId>
|
||||
<artifactId>taoyao-live</artifactId>
|
||||
<artifactId>taoyao-node</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -117,11 +115,6 @@
|
||||
<artifactId>taoyao-media</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.acgist</groupId>
|
||||
<artifactId>taoyao-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.acgist</groupId>
|
||||
<artifactId>taoyao-signal</artifactId>
|
||||
@@ -129,7 +122,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.acgist</groupId>
|
||||
<artifactId>taoyao-meeting</artifactId>
|
||||
<artifactId>taoyao-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<!-- 集合工具 -->
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.acgist</groupId>
|
||||
<artifactId>taoyao</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>taoyao-live</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>taoyao-live</name>
|
||||
<description>直播:直播、连麦</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.acgist</groupId>
|
||||
<artifactId>taoyao-media</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.acgist.taoyao.live;
|
||||
package com.acgist.taoyao.media.live;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.acgist.taoyao.live;
|
||||
package com.acgist.taoyao.media.live;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.acgist.taoyao.live;
|
||||
package com.acgist.taoyao.media.live;
|
||||
|
||||
import com.acgist.taoyao.boot.annotation.Manager;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.acgist.taoyao.live.controller;
|
||||
package com.acgist.taoyao.media.live.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
@@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.acgist.taoyao.boot.model.Message;
|
||||
import com.acgist.taoyao.live.Live;
|
||||
import com.acgist.taoyao.media.live.Live;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.acgist.taoyao.meeting;
|
||||
package com.acgist.taoyao.media.meeting;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.acgist.taoyao.meeting;
|
||||
package com.acgist.taoyao.media.meeting;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.acgist.taoyao.meeting;
|
||||
package com.acgist.taoyao.media.meeting;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.acgist.taoyao.meeting.controller;
|
||||
package com.acgist.taoyao.media.meeting.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -7,8 +7,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.acgist.taoyao.boot.model.Message;
|
||||
import com.acgist.taoyao.meeting.Meeting;
|
||||
import com.acgist.taoyao.meeting.MeetingManager;
|
||||
import com.acgist.taoyao.media.meeting.Meeting;
|
||||
import com.acgist.taoyao.media.meeting.MeetingManager;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.acgist.taoyao.meeting.listener;
|
||||
package com.acgist.taoyao.media.meeting.listener;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.acgist.taoyao.boot.annotation.EventListener;
|
||||
import com.acgist.taoyao.boot.model.Message;
|
||||
import com.acgist.taoyao.meeting.Meeting;
|
||||
import com.acgist.taoyao.meeting.MeetingListenerAdapter;
|
||||
import com.acgist.taoyao.media.meeting.Meeting;
|
||||
import com.acgist.taoyao.media.meeting.MeetingListenerAdapter;
|
||||
import com.acgist.taoyao.signal.event.meeting.MeetingCreateEvent;
|
||||
|
||||
/**
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.acgist.taoyao.meeting.listener;
|
||||
package com.acgist.taoyao.media.meeting.listener;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -6,8 +6,8 @@ import com.acgist.taoyao.boot.annotation.EventListener;
|
||||
import com.acgist.taoyao.boot.model.Message;
|
||||
import com.acgist.taoyao.boot.model.MessageCode;
|
||||
import com.acgist.taoyao.boot.model.MessageCodeException;
|
||||
import com.acgist.taoyao.meeting.Meeting;
|
||||
import com.acgist.taoyao.meeting.MeetingListenerAdapter;
|
||||
import com.acgist.taoyao.media.meeting.Meeting;
|
||||
import com.acgist.taoyao.media.meeting.MeetingListenerAdapter;
|
||||
import com.acgist.taoyao.signal.event.meeting.MeetingEnterEvent;
|
||||
|
||||
/**
|
||||
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.acgist</groupId>
|
||||
<artifactId>taoyao</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>taoyao-meeting</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>taoyao-meeting</name>
|
||||
<description>会议:会议模式、广播模式、单人对讲</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.acgist</groupId>
|
||||
<artifactId>taoyao-media</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -24,11 +24,7 @@
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.acgist</groupId>
|
||||
<artifactId>taoyao-live</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.acgist</groupId>
|
||||
<artifactId>taoyao-meeting</artifactId>
|
||||
<artifactId>taoyao-media</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user