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
1 changed files with 93 additions and 80 deletions

173
app.js
View File

@ -15,14 +15,28 @@ import path from 'path'
const __dirname = path.resolve()
import Server from 'socket.io'
import mediasoup, { getSupportedRtpCapabilities } from 'mediasoup'
import mediasoup from 'mediasoup'
let worker
let router = {}
let producerTransport
let consumerTransport
let producer
let consumer
/**
* videoCalls
* |-> Router
* |-> Producer
* |-> 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) => {
res.send('Hello from mediasoup app!')
@ -43,19 +57,8 @@ httpsServer.listen(process.env.PORT, () => {
})
const io = new Server(httpsServer)
// socket.io namespace (could represent a room?)
const peers = io.of('/mediasoup')
/**
* Worker
* |-> Router(s)
* |-> Producer Transport(s)
* |-> Producer
* |-> Consumer Transport(s)
* |-> Consumer
**/
const createWorker = async () => {
worker = await mediasoup.createWorker({
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 => {
console.log('[connection] socketId:', socket.id)
socket.emit('connection-success', {
socketId: socket.id,
existsProducer: producer ? true : false,
socketId: socket.id
})
socket.on('disconnect', () => {
// 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) => {
console.log('[createRoom] callId', callId);
console.log('Router length:', Object.keys(router).length);
if (router[callId] === undefined) {
// worker.createRouter(options)
// options = { mediaCodecs, appData }
// mediaCodecs -> defined above
// appData -> custom application data - we are not supplying any
// none of the two are required
router[callId] = await worker.createRouter({ mediaCodecs })
console.log(`[createRoom] Router ID: ${router[callId].id}`)
if (callId) {
console.log(`[createRoom] socket.id ${socket.id} callId ${callId}`);
if (!videoCalls[callId]) {
console.log('[createRoom] callId', callId);
videoCalls[callId] = { router: await worker.createRouter({ mediaCodecs }) }
console.log(`[createRoom] Router ID: ${videoCalls[callId].router.id}`);
}
socketDetails[socket.id] = callId;
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.
} 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
// We need to differentiate between the producer and consumer transports
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
// if sender is true, indicates a producer else a consumer
if (sender)
producerTransport = await createWebRtcTransportLayer(callId, callback)
videoCalls[callId].producerTransport = await createWebRtcTransportLayer(callId, callback)
else
consumerTransport = await createWebRtcTransportLayer(callId, callback)
videoCalls[callId].consumerTransport = await createWebRtcTransportLayer(callId, callback)
})
// see client's socket.emit('transport-connect', ...)
socket.on('transport-connect', async ({ dtlsParameters }) => {
console.log('[transport-connect] DTLS PARAMS... ', { dtlsParameters })
await producerTransport.connect({ dtlsParameters })
const callId = socketDetails[socket.id];
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', ...)
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
producer = await producerTransport.produce({
videoCalls[callId].producer = await videoCalls[callId].producerTransport.produce({
kind,
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)
// 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]
closeCall(callId);
})
})
// see client's socket.emit('transport-recv-connect', ...)
socket.on('transport-recv-connect', async ({ dtlsParameters }) => {
console.log(`[transport-recv-connect] DTLS PARAMS: ${dtlsParameters}`)
await consumerTransport.connect({ dtlsParameters })
const callId = socketDetails[socket.id];
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 {
// console.log('consume', rtpCapabilities, callId);
// check if the router can consume the specified producer
if (router[callId].canConsume({
producerId: producer.id,
if (videoCalls[callId].router.canConsume({
producerId: videoCalls[callId].producer.id,
rtpCapabilities
})) {
console.log('[consume] Can consume', callId);
// transport can now consume and return a consumer
consumer = await consumerTransport.consume({
producerId: producer.id,
videoCalls[callId].consumer = await videoCalls[callId].consumerTransport.consume({
producerId: videoCalls[callId].producer.id,
rtpCapabilities,
paused: true,
})
consumer.on('transportclose', () => {
videoCalls[callId].consumer.on('transportclose', () => {
const callId = socketDetails[socket.id];
console.log('transport close from consumer', callId)
// https://mediasoup.org/documentation/v3/mediasoup/api/#router-close
router[callId].close()
delete router[callId]
producer.close()
consumer.close()
videoCalls[callId].producer.close()
videoCalls[callId].consumer.close()
delete videoCalls[callId].router
})

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