LINXD-2209-black-screen-when-2-video-calls-are-answered-simultaneously #3

Merged
sergiu merged 31 commits from LINXD-2209-black-screen-when-2-video-calls-are-answered-simultaneously into master 2022-09-15 14:49:55 +00:00

173
app.js
View File

@ -15,14 +15,28 @@ import path from 'path'
const __dirname = path.resolve() const __dirname = path.resolve()
import Server from 'socket.io' import Server from 'socket.io'
import mediasoup, { getSupportedRtpCapabilities } from 'mediasoup' import mediasoup from 'mediasoup'
let worker let worker
let router = {} /**
let producerTransport * videoCalls
let consumerTransport * |-> Router
let producer * |-> Producer
let consumer * |-> Consumer
* |-> Producer Transport
* |-> Consumer Transport
*
* '<callId>': {
* router: Router,
* producer: Producer,
* producerTransport: Producer Transport,
* consumer: Consumer,
* consumerTransport: Consumer Transport
* }
*
**/
let videoCalls = {}
let socketDetails = {}
app.get('/', (_req, res) => { app.get('/', (_req, res) => {
res.send('Hello from mediasoup app!') res.send('Hello from mediasoup app!')
@ -43,19 +57,8 @@ httpsServer.listen(process.env.PORT, () => {
}) })
const io = new Server(httpsServer) const io = new Server(httpsServer)
// socket.io namespace (could represent a room?)
const peers = io.of('/mediasoup') const peers = io.of('/mediasoup')
/**
* Worker
* |-> Router(s)
* |-> Producer Transport(s)
* |-> Producer
* |-> Consumer Transport(s)
* |-> Consumer
**/
const createWorker = async () => { const createWorker = async () => {
worker = await mediasoup.createWorker({ worker = await mediasoup.createWorker({
rtcMinPort: 2000, rtcMinPort: 2000,
@ -96,40 +99,50 @@ const mediaCodecs = [
}, },
] ]
const closeCall = (callId) => {
if (videoCalls[callId]) {
videoCalls[callId].producer?.close();
videoCalls[callId].consumer?.close();
videoCalls[callId]?.consumerTransport.close();
videoCalls[callId]?.producerTransport.close();
videoCalls[callId].router.close();
delete videoCalls[callId].router;
}
}

not important: we don't need socket.id on clients

not important: we don't need socket.id on clients
const getRtpCapabilities = (callId, callback) => {
console.log('[getRtpCapabilities] callId', callId);
const rtpCapabilities = videoCalls[callId].router.rtpCapabilities;
callback({ rtpCapabilities });
}
peers.on('connection', async socket => { peers.on('connection', async socket => {
console.log('[connection] socketId:', socket.id) console.log('[connection] socketId:', socket.id)
socket.emit('connection-success', { socket.emit('connection-success', {
socketId: socket.id, socketId: socket.id
existsProducer: producer ? true : false,
}) })
socket.on('disconnect', () => { socket.on('disconnect', () => {
// do some cleanup // do some cleanup
console.log('peer disconnected') console.log('peer disconnected | socket.id', socket.id)
delete socketDetails[socket.id];
}) })
socket.on('createRoom', async ({ callId }, callback) => { socket.on('createRoom', async ({ callId }, callback) => {
console.log('[createRoom] callId', callId); if (callId) {
console.log('Router length:', Object.keys(router).length); console.log(`[createRoom] socket.id ${socket.id} callId ${callId}`);
if (router[callId] === undefined) { if (!videoCalls[callId]) {
// worker.createRouter(options) console.log('[createRoom] callId', callId);
// options = { mediaCodecs, appData } videoCalls[callId] = { router: await worker.createRouter({ mediaCodecs }) }
// mediaCodecs -> defined above console.log(`[createRoom] Router ID: ${videoCalls[callId].router.id}`);
// appData -> custom application data - we are not supplying any }
// none of the two are required socketDetails[socket.id] = callId;
router[callId] = await worker.createRouter({ mediaCodecs }) getRtpCapabilities(callId, callback);
cristi marked this conversation as resolved Outdated

For refactor: why do we need to differentiate between producerTransport and consumerTransport? Why don't we have a transport variable (it is only one transport for each client regardless if they are producer or consumer)?

For refactor: why do we need to differentiate between producerTransport and consumerTransport? Why don't we have a `transport` variable (it is only one transport for each client regardless if they are producer or consumer)?

We need producer and consumer transports for each call.

We need producer and consumer transports for each call.
console.log(`[createRoom] Router ID: ${router[callId].id}`) } else {
console.log(`[createRoom] missing callId ${callId}`);
} }
getRtpCapabilities(callId, callback)
}) })
const getRtpCapabilities = (callId, callback) => {
const rtpCapabilities = router[callId].rtpCapabilities
callback({ rtpCapabilities })
}
// Client emits a request to create server side Transport // Client emits a request to create server side Transport
// We need to differentiate between the producer and consumer transports // We need to differentiate between the producer and consumer transports
socket.on('createWebRtcTransport', async ({ sender, callId }, callback) => { socket.on('createWebRtcTransport', async ({ sender, callId }, callback) => {
@ -137,93 +150,95 @@ peers.on('connection', async socket => {
// The client indicates if it is a producer or a consumer // The client indicates if it is a producer or a consumer
// if sender is true, indicates a producer else a consumer // if sender is true, indicates a producer else a consumer
if (sender) if (sender)
producerTransport = await createWebRtcTransportLayer(callId, callback) videoCalls[callId].producerTransport = await createWebRtcTransportLayer(callId, callback)
else else
consumerTransport = await createWebRtcTransportLayer(callId, callback) videoCalls[callId].consumerTransport = await createWebRtcTransportLayer(callId, callback)
}) })
// see client's socket.emit('transport-connect', ...) // see client's socket.emit('transport-connect', ...)
socket.on('transport-connect', async ({ dtlsParameters }) => { socket.on('transport-connect', async ({ dtlsParameters }) => {
console.log('[transport-connect] DTLS PARAMS... ', { dtlsParameters }) const callId = socketDetails[socket.id];
await producerTransport.connect({ dtlsParameters }) console.log(`[transport-connect] socket.id ${socket.id} | callId ${callId} | DTLS PARAMS... ${dtlsParameters}`)
await videoCalls[callId].producerTransport.connect({ dtlsParameters })
}) })
// see client's socket.emit('transport-produce', ...) // see client's socket.emit('transport-produce', ...)
socket.on('transport-produce', async ({ kind, rtpParameters, appData }) => { socket.on('transport-produce', async ({ kind, rtpParameters, appData }) => {
const callId = socketDetails[socket.id];
console.log('[transport-produce] | socket.id', socket.id, '| callId', callId);
// call produce based on the prameters from the client // call produce based on the prameters from the client
producer = await producerTransport.produce({ videoCalls[callId].producer = await videoCalls[callId].producerTransport.produce({
kind, kind,
rtpParameters, rtpParameters,
}) })
console.log(`[transport-produce] Producer ID: ${producer.id} | kind: ${producer.kind}`) console.log(`[transport-produce] Producer ID: ${videoCalls[callId].producer.id} | kind: ${videoCalls[callId].producer.kind}`)
producer.on('transportclose', () => { videoCalls[callId].producer.on('transportclose', () => {
const callId = socketDetails[socket.id];
console.log('transport for this producer closed', callId) console.log('transport for this producer closed', callId)
closeCall(callId);
// https://mediasoup.org/documentation/v3/mediasoup/api/#producer-close
producer.close()
// https://mediasoup.org/documentation/v3/mediasoup/api/#router-close
router[callId].close()
delete router[callId]
}) })
}) })
// see client's socket.emit('transport-recv-connect', ...) // see client's socket.emit('transport-recv-connect', ...)
socket.on('transport-recv-connect', async ({ dtlsParameters }) => { socket.on('transport-recv-connect', async ({ dtlsParameters }) => {
console.log(`[transport-recv-connect] DTLS PARAMS: ${dtlsParameters}`) const callId = socketDetails[socket.id];
await consumerTransport.connect({ dtlsParameters }) console.log(`[transport-recv-connect] socket.id ${socket.id} | callId ${callId} | DTLS PARAMS: ${dtlsParameters}`);
await videoCalls[callId].consumerTransport.connect({ dtlsParameters })
}) })
socket.on('consume', async ({ rtpCapabilities, callId }, callback) => { socket.on('consume', async ({ rtpCapabilities }, callback) => {
const callId = socketDetails[socket.id];
console.log('[consume] callId', callId);
try { try {
// console.log('consume', rtpCapabilities, callId); // console.log('consume', rtpCapabilities, callId);
// check if the router can consume the specified producer // check if the router can consume the specified producer
if (router[callId].canConsume({ if (videoCalls[callId].router.canConsume({
producerId: producer.id, producerId: videoCalls[callId].producer.id,
rtpCapabilities rtpCapabilities
})) { })) {
console.log('[consume] Can consume', callId);
// transport can now consume and return a consumer // transport can now consume and return a consumer
consumer = await consumerTransport.consume({ videoCalls[callId].consumer = await videoCalls[callId].consumerTransport.consume({
producerId: producer.id, producerId: videoCalls[callId].producer.id,
rtpCapabilities, rtpCapabilities,
paused: true, paused: true,
}) })
consumer.on('transportclose', () => { videoCalls[callId].consumer.on('transportclose', () => {
const callId = socketDetails[socket.id];
console.log('transport close from consumer', callId) console.log('transport close from consumer', callId)
// https://mediasoup.org/documentation/v3/mediasoup/api/#router-close // https://mediasoup.org/documentation/v3/mediasoup/api/#router-close
router[callId].close() videoCalls[callId].producer.close()
delete router[callId] videoCalls[callId].consumer.close()
producer.close() delete videoCalls[callId].router
consumer.close()
}) })

Function to clearVideoCall and socket.

Function to clearVideoCall and socket.
consumer.on('producerclose', () => { videoCalls[callId].consumer.on('producerclose', () => {
const callId = socketDetails[socket.id];
console.log('producer of consumer closed', callId) console.log('producer of consumer closed', callId)
// https://mediasoup.org/documentation/v3/mediasoup/api/#router-close // https://mediasoup.org/documentation/v3/mediasoup/api/#router-close
router[callId].close() closeCall()
delete router[callId]
producer.close()
consumer.close()
}) })
// from the consumer extract the following params // from the consumer extract the following params
// to send back to the Client // to send back to the Client
const params = { const params = {
id: consumer.id, id: videoCalls[callId].consumer.id,
producerId: producer.id, producerId: videoCalls[callId].producer.id,
kind: consumer.kind, kind: videoCalls[callId].consumer.kind,
rtpParameters: consumer.rtpParameters, rtpParameters: videoCalls[callId].consumer.rtpParameters,
} }
// send the parameters to the client // send the parameters to the client
callback({ params }) callback({ params })
} else {
console.log('[canConsume] Can\'t consume')
} }
} catch (error) { } catch (error) {
console.log(error.message) console.log('[consume] Error', error.message)
callback({ callback({
params: { params: {
error: error error: error
@ -233,8 +248,9 @@ peers.on('connection', async socket => {
}) })
socket.on('consumer-resume', async () => { socket.on('consumer-resume', async () => {
console.log(`[consumer-resume]`) const callId = socketDetails[socket.id];
await consumer.resume() console.log(`[consumer-resume] callId ${callId}`)
await videoCalls[callId].consumer.resume()
}) })
}) })
@ -255,10 +271,9 @@ const createWebRtcTransportLayer = async (callId, callback) => {
} }
// console.log('webRtcTransport_options', webRtcTransport_options); // console.log('webRtcTransport_options', webRtcTransport_options);
// console.log('router', router, '| router[callId]', router[callId]);
// https://mediasoup.org/documentation/v3/mediasoup/api/#router-createWebRtcTransport // https://mediasoup.org/documentation/v3/mediasoup/api/#router-createWebRtcTransport
let transport = await router[callId].createWebRtcTransport(webRtcTransport_options) let transport = await videoCalls[callId].router.createWebRtcTransport(webRtcTransport_options)
console.log(`callId: ${callId} | transport id: ${transport.id}`) console.log(`callId: ${callId} | transport id: ${transport.id}`)
transport.on('dtlsstatechange', dtlsState => { transport.on('dtlsstatechange', dtlsState => {
@ -278,8 +293,6 @@ const createWebRtcTransportLayer = async (callId, callback) => {
dtlsParameters: transport.dtlsParameters, dtlsParameters: transport.dtlsParameters,
} }
// console.log('params', params);
// send back to the client the following prameters // send back to the client the following prameters
callback({ callback({
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#TransportOptions // https://mediasoup.org/documentation/v3/mediasoup-client/api/#TransportOptions